﻿<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* ============================== *\
			 page base
\* ============================== */

body {
	background-color: #F0F0F0;
	margin: 0px;
	overflow: hidden;
}

.page-base {
	position: absolute;
	display: flex;
}

#splitter {
	width: 5px;
	cursor: col-resize;
	box-shadow: 0 0 4px #B0B0B0;
}

.page-container {
	margin: 7.5px;
	background-color: white;
	overflow: auto;
	flex-grow: 1;
}
#page-select-container {
	white-space: nowrap;
}

#page-display-container-body {
	padding: 7px;
	width: fit-content;
}

/* ============================== *\
		    page select
\* ============================== */

.arrow-page-root {
	cursor: pointer;
}
.dot-page-root {
	cursor: default;
	margin-left: 5px;
	margin-right: 6px;
}

.ps-root-container {
	margin: 7px;
}

.ps-folder {
	display: table;
}
.ps-folder-name {
	cursor: pointer;
	padding: 1;
}
.ps-container {
	margin-left: 7px;
	padding-left: 7px;
	border-left: thin solid;
	border-color: #80808080;
}

.ps-page {
}
.ps-page-name {
	cursor: pointer;
	padding: 1px;
}

/* ============================== *\
	       page display
\* ============================== */

blockquote > *:first-child {
	margin-top: 0;
}
blockquote > *:last-child {
	margin-bottom: 0;
}
blockquote {
	margin-left: 0;
	margin-right: 0;
	padding: 12px;
	border-left: 5px solid #d0d0d0;
	background-color: #f7f7f7;
}

.smart-link {
	color: blue;
	cursor: pointer;
}

.spoiler {
	margin-left: 8px;
}
.spoiler-wrap {
	border: solid;
	border-width: 0px;
	padding-left: 2px;
}
.spoiler-name {
	border: solid;
	border-width: 0px;
}

.code-block {
	display: inline-block;
	cursor: text;
	border-radius: 3px;
	
	padding: 4px;
	padding-right: 12px;
	
	margin: 0;
	
	position: relative;
}
.code-block-copier {
	cursor: pointer;
	padding: 2px;
	
	position: absolute;
	top: 0;
	
	border: thin solid;
	border-radius: 3.7px 0px 3.7px 3.7px;
	box-shadow: -2px 2px 3px #0000004a;
	background: linear-gradient(0deg, #f0f0f0, white);
}
.inline-code, .code-block {
	background: #F0F0F0;
}
blockquote .inline-code,
blockquote .code-block {
	background: #DEDEDE;
}

.code-keyword {
	font-weight: bold;
}
.code-build-in {
	color: blue;
}
.code-red {
	color: red;
	font-weight: bold;
}
.code-glowing-bracket {
	color: red;
	background-color: #C0C0C0;
}
</style>
</head>
<body>
<div class="page-base" id="page-select">
	<div class="page-container" id="page-select-container"></div>
</div>
<div class="page-base" id="page-display">
	<div class="page-container" id="page-display-container">
		<div id="page-display-container-body"></div>
	</div>
</div>
<div class="page-base" id="splitter"></div>
<script>
/* ============================== *\
		        misc
\* ============================== */

const selected_name_color = "#eaeaea";
const  pointed_name_color = "#d1faff";

var localStorageKey = "";

const get_style = (el)=>el.currentStyle || window.getComputedStyle(el);

var spl_resize_event = [];

/* ============================== *\
		     page load
\* ============================== */

var root_folder = null;
var currently_loading_folder = null;

var prev_opened_folders = null;
var opened_folders = [];

var page_by_path = {};
const define_page_with_path = (path, page)=>{
	
	if (page_by_path[path])
		console.error(`page with path "${path}" defined multiple times`); else
		page_by_path[path] = page;
	
	if (broken_links[path])
	{
		for (let lnk of broken_links[path])
			lnk.fix(page);
		delete broken_links[path];
	}
	
}

const on_start_folder = (folder_name, root_page)=>{
	
	let res = {
		folders: [],
		pages: [],
		container: document.createElement("div"),
		name: folder_name,
	};
	
	if (!root_folder)
	{
		root_folder = res;
		res.path = "";
		
		localStorageKey = `reference : ${folder_name} : `;
		let prev_opened_folders_str = localStorage[localStorageKey+"opened_folders"];
		prev_opened_folders = prev_opened_folders_str ? JSON.parse(prev_opened_folders_str) : [];
		
		res.container.className = "ps-root-container";
		document.getElementById("page-select-container").append(res.container);
	}
	else
	{
		if (!root_page) {
			res.auto_fill_content = true;
			root_page = document.createElement("div");
			root_page.hidden = true;
		}
		
		currently_loading_folder.folders.push(res);
		res.root = currently_loading_folder;
		res.path = res.root.path + folder_name + '/';
		
		res.body = document.createElement("div");
		res.body.className = "ps-folder";
		{
			let ind = prev_opened_folders.indexOf(res.path);
			res.state = ind!==-1;
			if (res.state)
			{
				prev_opened_folders.splice(ind,1);
				opened_folders.push(res.path);
			}
		}
		currently_loading_folder.container.append(res.body);
		
		res.update = ()=>{
			res.state_span.innerHTML = String.fromCharCode( res.empty? 0x2022 : res.state ? 0x25BC : 0x25BA );
			res.container.hidden = !res.state;
		}
		res.reverse_state = ()=>{
			res.state = !res.state;
			
			if (res.state)
				opened_folders.push(res.path); else
				opened_folders.splice(opened_folders.indexOf(res.path), 1);
			localStorage[localStorageKey+"opened_folders"] = JSON.stringify(opened_folders);
			
			res.update();
		};
		
		res.state_span = document.createElement("span");
		res.state_span.className = "dot-page-root";
		res.state_span.innerHTML = String.fromCharCode( 0x2022 );
		res.body.append(res.state_span);
		
		res.name_span = document.createElement("span");
		res.name_span.className = "ps-folder-name";
		res.name_span.innerHTML = folder_name;
		root_page.name_span = res.name_span;
		res.body.append(res.name_span);
		//
		res.name_span.addEventListener("click", ()=>select_page(root_page));
		res.name_span.addEventListener("mouseenter", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor=pointed_name_color; });
		res.name_span.addEventListener("mouseleave", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor="inherit"; });
		
		res.container.className = "ps-container";
		res.body.append(res.container);
		
	}
	currently_loading_folder = res;
	
	if (root_folder !== res) {
		res.content_ref = root_page;
		root_page.tree_obj = res;
		document.getElementById("page-display-container-body").append(root_page);
		add_to_switch_list(root_page);
		if (!res.auto_fill_content)
			fix_links(root_page);
	}
	
}

const on_page_added = (page)=>{
	
	let res = {
		root: currently_loading_folder,
		name: page.getAttribute("page_name"),
	};
	res.path = currently_loading_folder.path + res.name;
	
	res.cont_div = document.createElement("div");
	res.cont_div.className = "ps-page";
	currently_loading_folder.container.append(res.cont_div);
	
	res.dot_span = document.createElement("span");
	res.dot_span.className = "dot-page-root";
	res.dot_span.innerHTML = String.fromCharCode( 0x2022 );
	res.cont_div.append(res.dot_span);
	
	res.name_span = document.createElement("span");
	res.name_span.innerHTML = res.name + "<br>";
	res.name_span.className = "ps-page-name";
	res.name_span.addEventListener("click", ()=>select_page(page));
	res.name_span.addEventListener("mouseenter", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor=pointed_name_color; });
	res.name_span.addEventListener("mouseleave", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor="inherit"; });
	res.cont_div.append(res.name_span);
	page.name_span = res.name_span;
	
	res.content_ref = page;
	page.tree_obj = res;
	document.getElementById("page-display-container-body").append(page);
	
	currently_loading_folder.pages.push(res);
	define_page_with_path(res.path, page);
	add_to_switch_list(page);
	fix_links(page);
	
	if (!selected_page && !document.location.hash) select_page(page);
}

const on_end_folder = ()=>{
	
	if (currently_loading_folder !== root_folder) {
		let curr_folder = currently_loading_folder;
		let root_page = curr_folder.content_ref;
		
		if ( curr_folder.folders.length || curr_folder.pages.length )
		{
			curr_folder.state_span.className = "arrow-page-root";
			curr_folder.state_span.addEventListener("click", curr_folder.reverse_state);
			curr_folder.name_span.addEventListener("dblclick", curr_folder.reverse_state);
			curr_folder.update();
		} else
			curr_folder.empty = true;
		
		if (curr_folder.auto_fill_content) {
			
			if (curr_folder.folders.length)
			{
				let h = document.createElement("h1");
				h.innerHTML = "Под-папки:";
				root_page.append(h);
				
				let l = document.createElement("ul");
				for (let folder of curr_folder.folders)
				{
					let li = document.createElement("li");
					li.innerHTML = folder.name_span.innerHTML;
					make_smart_link(li, folder.content_ref);
					l.append(li);
				}
				root_page.append(l);
				
			}
			
			if (curr_folder.pages.length)
			{
				let h = document.createElement("h1");
				h.innerHTML = "Страницы:";
				root_page.append(h);
				
				let l = document.createElement("ul");
				for (let page of curr_folder.pages)
				{
					let li = document.createElement("li");
					li.innerHTML = page.name_span.innerHTML;
					make_smart_link(li, page.content_ref);
					l.append(li);
				}
				root_page.append(l);
				
			}
			
		}
		
		define_page_with_path(curr_folder.path, root_page);
		currently_loading_folder = curr_folder.root;
	} else if (!selected_page)
		select_page(root_folder.content_ref);
	
}

/* ============================== *\
	        page display
\* ============================== */

var selected_page = null;
var scroll_by_page = {};
const is_selected = (name_span)=>(!selected_page || selected_page.name_span!==name_span);
const select_page = (new_page)=>{
	if (selected_page == new_page) return;
	document.location.hash = new_page.tree_obj.path;
	fix_element(new_page);
	
	let container = document.getElementById("page-display-container");
	
	if (selected_page)
	{
		scroll_by_page[selected_page.tree_obj.path] = container.scrollTop;
		selected_page.hidden = true;
		let name_span = selected_page.name_span;
		if (name_span) name_span.style.backgroundColor = "inherit";
	}
	
	selected_page = new_page;
	if (selected_page)
	{
		selected_page.hidden = false;
		container.focus({preventScroll: true});
		let name_span = selected_page.name_span;
		if (name_span) name_span.style.backgroundColor = selected_name_color;
		
		let tree_obj = new_page.tree_obj.root;
		while (tree_obj)
		{
			if ( tree_obj.state==false && tree_obj.reverse_state ) tree_obj.reverse_state();
			tree_obj = tree_obj.root;
		}
		
		container.scrollTop = scroll_by_page[selected_page.tree_obj.path] || 0;
	}
}

window.addEventListener("keyup", e=>{
	if (e.code==="ArrowLeft") {
		if (p = selected_page?.page_l) select_page(p);
	} else
	if (e.code==="ArrowRight") {
		if (p = selected_page?.page_r) select_page(p);
	}
});

const make_smart_link = (lnk, page)=>{
	lnk.className = "smart-link";
	lnk.addEventListener("click", ()=>select_page(page));
}

var broken_links = {};
const define_broken_lnk = (path, source, fixer)=>{
	if (!broken_links[path]) broken_links[path] = [];
	broken_links[path].push({
		source:	source,
		fix:	fixer,
	});
}

const add_to_switch_list = (o)=>{
	
	if (add_to_switch_list.last_o) {
		o.page_l = add_to_switch_list.last_o;
		add_to_switch_list.last_o.page_r = o;
	}
	add_to_switch_list.last_o = o;
	
}
const fix_links = (page)=>{
	
	for (let lnk of page.getElementsByTagName('a'))
	{
		lnk.path = lnk.getAttribute("path");
		if (!lnk.path) continue;
		lnk.removeAttribute("path");
		
		start_folder = currently_loading_folder;
		while (lnk.path.startsWith("../"))
		{
			start_folder = start_folder.root;
			lnk.path = lnk.path.substr(3);
		}
		lnk.path = start_folder.path+lnk.path;
		
		let lnk_page = page_by_path[lnk.path];
		if (lnk_page)
			make_smart_link(lnk, lnk_page); else
			define_broken_lnk(lnk.path, page.tree_obj.path, new_page=>make_smart_link(lnk, new_page))
		
	}
	
}

const code_words_color = {
	"pas": {
		"keyword": [
			"###", "##", "program", "unit", "library", "namespace",
			"begin","end", "var", "uses", "as", "new", "try", "except", "on", "do", "finally", "const",
			"procedure", "function", "lock", "type", "class","record", "sizeof","typeof", "external",
			"static", "array", "where", "or","and", "public","private", "property","constructor", "default",
			"of", "if","then","else", "div","mod", "extensionmethod", "while",
		],
		"build-in": [
			"nil", "self", "true", "false", "value",
			"string","char", "object", "pointer", "single","real",
			"shortint", "smallint", "integer", "int64",
			"byte", "word", "longword", "cardinal", "uint64",
		],
		"red": ["ToDo"],
	},
	"cl-c": {
		"keyword": ["__kernel", "__global"],
		"build-in": ["int", "void"],
	},
	"default": {
		
	}
}

const fix_element = (page)=>{
	if (page.fixed) return;
	page.fixed = true;
	
	for (let spoiler of page.getElementsByClassName('spoiler'))
	{
		let get_spoiler_text = ()=> + ' ' + spoiler.getAttribute('summary');
		
		let wrap = document.createElement('p');
		wrap.className = "spoiler-wrap";
		wrap.update = ()=>{
			wrap.state_span.innerHTML = String.fromCharCode( 	spoiler.hidden ? 0x25BA	: 0x25BC );
			wrap.style.borderLeftWidth = 						spoiler.hidden ? 0 		: 1;
			wrap.name_span.style.borderBottomWidth = 			spoiler.hidden ? 1 		: 0;
			wrap.style.marginLeft = 							spoiler.hidden ? 1		: 0;
		}
		
		wrap.state_span = document.createElement("span");
		wrap.append(wrap.state_span);
		
		wrap.name_span = document.createElement("span");
		wrap.name_span.className = "spoiler-name";
		wrap.name_span.innerHTML = spoiler.getAttribute("summary");
		wrap.append(wrap.name_span);
		
		wrap.update();
		wrap.reverse_state = ()=>{
			spoiler.hidden = !spoiler.hidden;
			wrap.update();
		}
		
		wrap.state_span.addEventListener("click", wrap.reverse_state);
		wrap.name_span.addEventListener("click", wrap.reverse_state);
		
		wrap.state_span.style.cursor = "pointer";
		wrap.name_span.style.cursor = "pointer";
		
		spoiler.replaceWith(wrap);
		wrap.append(spoiler);
	}
	
	for (let code of page.getElementsByTagName('code'))
	{
		let w_to_regex = (w)=>`(?<!\\w)${w}(?!\\w)`;
		
		let code_html = code.innerHTML;
		
		// Автоопределение языка кода
		if (!code.className)
		{
			let best = {lang: null, c: 0};
			let multiple_best = true;
			
			for (let lang_name in code_words_color)
			{
				let c = 0;
				for (let wordt in code_words_color[lang_name])
					for (let w of code_words_color[lang_name][wordt])
					{
						var m = code_html.match(new RegExp( w_to_regex(w), "gi" ));
						if (m) c += m.length;
					}
				
				if (best.c == c)
					multiple_best = true; else
				if (best.c < c)
				{
					multiple_best = false;
					best.lang = lang_name;
					best.c = c;
				}
			}
			
			if (multiple_best)
				code.className = "language-default"; else
				code.className = "language-" + best.lang;
		}
		
		// Подсветка особых слов в коде
		{
			let lang = code.className.substr("language-".length);
			let curr_cw = code_words_color[lang];
			if (!curr_cw) curr_cw = code_words_color["default"];
			for (let wordt in curr_cw)
				code_html = code_html.replace(
					new RegExp(curr_cw[wordt].map(w_to_regex).join('|'),"gi"),
					w=> `<span class="code-${wordt}">${w}</span>`
				);
		}
		
		// Выделение скобок
		{
			var br_types = {
				op: ["(", "[", "{", "&lt;", "'"],
				cl: [")", "]", "}", "&gt;", "'"],
			}
			for (let op in br_types)
				for (let i=0; i<br_types[op].length; i++)
					code_html = code_html.replace(
						new RegExp('\\'+br_types[op][i], "g"),
						`<span class=bracket ${ op=="op" ? "op=true" : "" } bt=${i}>${br_types[op][i]}</span>`
					);
			
		}
		
		// Выделение первой строки
		if (code.parentElement.tagName == "PRE")
		{
			let ind = code_html.indexOf('\n');
			if (ind != code_html.length-1)
				code_html = `<span class="code-first-line">${code_html.slice(0,ind)}</span>${code_html.slice(ind)}`;
		}
		
		code.innerHTML = code_html;
		code.firstLine = code.children[0];
		if (code.firstLine && code.firstLine.className != "code-first-line") delete code.firstLine;
		
		// Подсветка скобок
		{
			let br_st = [];
			for (let obj2 of code.getElementsByClassName("bracket"))
			{
				let b2t = obj2.getAttribute("bt");
				
				if (obj2.getAttribute("op"))
					br_st.push({
						obj: obj2,
						bt: b2t,
					}); else
				{
					let b1 = br_st.pop();
					let b0 = null;
					if (!b1) continue;
					if (b1.obj == obj2.parentElement)
					{
						b0 = b1;
						b1 = br_st.pop();
					}
					if (!b1) continue;
					if (b1.bt == b2t)
					{
						let obj1 = b1.obj;
						
						let on_enter = ()=>{
							obj1.className = "code-glowing-bracket";
							obj2.className = "code-glowing-bracket";
						}
						let on_leave = ()=>{
							obj1.className = null;
							obj2.className = null;
						}
						
						obj1.addEventListener("mouseenter", on_enter);
						obj2.addEventListener("mouseenter", on_enter);
						obj1.addEventListener("mouseleave", on_leave);
						obj2.addEventListener("mouseleave", on_leave);
					} else
					{
						br_st.push(b1);
						if (b0) br_st.push(b0);
					}
				}
				
			}
			
		}
		
		if (code.parentElement.tagName == "PRE")
		{
			let pre = code.parentElement;
			pre.className = 'code-block';
			
			let wrap = document.createElement('p');
			pre.replaceWith(wrap);
			wrap.append(pre);
			
		} else
			code.className = "inline-code";
		
	}
	
	page.style.visibility = "hidden";
	page.hidden = false;
	
	for (let code of page.getElementsByTagName('code'))
	{
		if (code.firstLine)
		{
			let pre = code.parentElement;
			
			let copy_button = document.createElement('div');
			copy_button.className = "code-block-copier";
			copy_button.innerText = "Копировать";
			copy_button.style.visibility = "hidden";
			pre.append(copy_button);
			
			copy_button.addEventListener("click", ()=>{
				
				if (document.selection) { // IE
					var range = document.body.createTextRange();
					range.moveToElementText(code);
					range.select();
				} else if (window.getSelection) { // other browsers
					var range = document.createRange();
					range.selectNode(code);
					window.getSelection().removeAllRanges();
					window.getSelection().addRange(range);
				}
				
				var text = code.innerText;
				navigator.clipboard.writeText(text.substring(0,text.length-1));
			});
			
			copy_button.reset_right = ()=>{
				pre_st = get_style(pre);
				let container = document.getElementById("page-display-container");
				let x = pre.getBoundingClientRect().right - (container.getBoundingClientRect().left+container.clientWidth);
				if (x<0) x = 0; else
				{
					let max_x = pre.offsetWidth - copy_button.offsetWidth;
					if (x>max_x) x = max_x;
				}
				copy_button.style.right = x;
			};
			let try_reset_right = ()=>{
				if (!copy_button.hidden)
					copy_button.reset_right();
			};
			spl_resize_event.push(try_reset_right);
			window.addEventListener("resize", try_reset_right);
			
			pre.addEventListener("mouseenter", ()=>{
				copy_button.hidden = false;
				copy_button.reset_right();
			});
			pre.addEventListener("mouseleave", ()=>copy_button.hidden = true);
			
			copy_button.addEventListener("mousedown",	()=>copy_button.style.background = "linear-gradient(180deg, rgb(240, 240, 240), white)");
			copy_button.addEventListener("mouseup",		()=>copy_button.style.background = "linear-gradient(  0deg, rgb(240, 240, 240), white)");
			copy_button.addEventListener("mouseleave",	()=>copy_button.style.background = "linear-gradient(  0deg, rgb(240, 240, 240), white)");
			
			pre.style.minWidth =
				code.firstLine.offsetWidth +
				copy_button.offsetWidth +
				0
			;
			
			copy_button.hidden = true;
			copy_button.style.visibility = null;
		}
		
	}
	
	page.hidden = true;
	page.style.visibility = null;
}

/* ============================== *\
		        init
\* ============================== */

{
	if (document.location.hash)
	{
		let get_hash = ()=>decodeURIComponent(document.location.hash.substr(1));
		let define_hash_lnk = ()=>define_broken_lnk(
			get_hash(),
			"document.location.hash",
			page=>select_page(page)
		);
		define_hash_lnk();
		window.addEventListener("hashchange", ()=>{
			let hash_page = page_by_path[get_hash()];
			if (hash_page)
				select_page(hash_page); else
				define_hash_lnk();
		});
	}
	
	let page_select = document.getElementById("page-select");
	let page_display = document.getElementById("page-display");
	let splitter = document.getElementById("splitter");
	
	let ww = window.innerWidth;
	let wh = window.innerHeight;
	
	for (let cont of document.getElementsByClassName("page-container"))
	{
		let par = cont.parentElement;
		par.update_cont = (w)=>{
			cont.style.width = w-15;
			cont.style.height = wh-15;
		};
	}
	
	let spl_X = ww * 0.30;
	const reset_spl = ()=>{
		ww = window.innerWidth;
		wh = window.innerHeight;
		
		page_select.style.height = wh + "px";
		splitter.style.height = wh + "px";
		page_display.style.height = wh + "px";
		
		if (ww-5<spl_X) spl_X = ww-5;
		if (spl_X<5) spl_X = 5;
		
		page_select.style.width = spl_X + "px";
		splitter.style.left = spl_X + "px";
		let x2 = spl_X+splitter.clientWidth;
		let w2 = ww - x2;
		page_display.style.left = x2 + "px";
		page_display.style.width = w2 + "px";
		
		page_select.update_cont(spl_X);
		page_display.update_cont(w2);
		
		for (let handler of spl_resize_event)
			handler();
	}
	reset_spl();
	
	window.addEventListener("resize", ()=>reset_spl());
	
	splitter.addEventListener("dblclick", ()=>{
		let w = 0;
		let psc = document.getElementsByClassName("page-container")[0];
		let psc2 = psc.children[0];
		for (let n of psc2.children)
			if (n.clientWidth>w) w = n.clientWidth;
		
		let get_margin = (el)=>{
			let style = get_style(el);
			return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
		}
		
		spl_X = w +
			get_margin(psc) +
			get_margin(psc2)
		;
		reset_spl();
	});
	
	let spl_grabed = false;
	splitter.addEventListener("mousedown", (e)=>{
		spl_grabed=true;
		e.preventDefault();
	});
	window.addEventListener("mousemove", (e)=>{if (spl_grabed) {
		spl_X = e.clientX - splitter.clientWidth/2;
		reset_spl();
		e.preventDefault();
	}});
	window.addEventListener("mouseup", ()=>spl_grabed=false);
	
}

window.onload = ()=>{
	
	for (path in broken_links)
		console.error(`Page "${path}" referenced ${broken_links[path].length} times but not found:`, broken_links[path].map(lnk=>lnk.source));
	
	localStorage[localStorageKey+"opened_folders"] = JSON.stringify(opened_folders);
	if (prev_opened_folders.length) console.log("folders were marked as opened, but not found:", prev_opened_folders);
	delete prev_opened_folders;
	
}
</script>
<script>on_start_folder("CL ABC", null)</script>
<script>on_start_folder("Общие сведения", null)</script>
<div id="page-1" page_name="О справке" hidden=true>
<p>Данная справка относится к модулю <code>OpenCLABC</code>, входящему в состав стандартных модулей языка <code>PascalABC.NET</code>.</p>
<p>Модуль <code>OpenCLABC</code> это высокоуровневая обёртка модуля <code>OpenCL</code>.<br />
Это значит, что с <code>OpenCLABC</code> можно писать гораздо меньше кода в больших и сложных программах,
однако такой же уровень микроконтроля как с модулем <code>OpenCL</code> недоступен.
Например, напрямую управлять <code>cl_event</code>'ами в <code>OpenCLABC</code> невозможно.
Вместо этого надо использовать операции с очередями (например, сложение и умножение очередей)</p>
<p>Справка модуля <code>OpenCL</code> отсутствует. Вместо неё смотрите:</p>
<ul>
<li><a href="%D0%93%D0%B0%D0%B9%D0%B4%20%D0%BF%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E%20OpenGL%20%D0%B8%20OpenCL.html">Общий гайд по использованию модулей <code>OpenGL</code> и <code>OpenCL</code></a></li>
<li><a href="https://www.khronos.org/registry/OpenCL/">Контейнер справок библиотеки <code>OpenCL</code>, на которой основан модуль <code>OpenCL</code></a></li>
</ul>
<p>Если в справке или модуле найдена ошибка, или чего-либо не хватает - пишите в <a href="https://github.com/SunSerega/POCGL/issues">issue</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-1"))</script>
<div id="page-2" page_name="Термины, которые часто путают новички" hidden=true>
<ul>
<li><p>CPU — Центральное Процессорное Устройство (процессор);</p>
</li>
<li><p>GPU — Графическое Процессорное Устройство (видеокарта);</p>
</li>
<li><p>RAM — Оперативная память;</p>
</li>
<li><p>Команда — запрос на выполнение чего-либо. К примеру:</p>
<ul>
<li>Запрос на запуск программы на GPU;</li>
<li>Запрос на начало чтения данных из памяти GPU в оперативную память;</li>
</ul>
<p><strong>Называть процедуры и функции командами ошибочно!</strong></p>
</li>
<li><p>Подпрограмма — процедура или функция;</p>
</li>
<li><p>Метод — особая подпрограмма, вызываемая через экземпляр:</p>
<ul>
<li>К примеру, метод <code>Context.SyncInvoke</code> выглядит в коде как <code>cont.SyncInvoke(...)</code>, где <code>cont</code> — переменная типа <code>Context</code>;</li>
</ul>
</li>
</ul>
<p>Остальные непонятные термины можно найти в справке <code>PascalABC.NET</code> или в интернете.</p>
</div>
<script>on_page_added(document.getElementById("page-2"))</script>
<script>on_end_folder()</script>
<div id="page-3" page_name="" hidden=true>
<p>Сам модуль <code>OpenCLABC</code> - обёртка модуля <code>OpenCL</code>. Это значит, что внутри он использует
содержимое <code>OpenCL</code>, но при подключении - показывает только свой личный функционал.</p>
<p>Так же множество типов из <code>OpenCLABC</code> являются обёртками типов из <code>OpenCL</code>.</p>
<p>Тип <code>CommandQueue</code> использует тип <code>cl_command_queue</code>, но предоставляет очень много не связанного с <code>cl_command_queue</code> функционала.</p>
<p>&quot;Простые&quot; типы-обёртки модуля <code>OpenCLABC</code> предоставляют только функционал соответствующего
типа из модуля <code>OpenCL</code>... в более презентабельном виде. Из общего - у таких типов есть:</p>
<ul>
<li><p>Свойство <code>.Native</code>, возвращающее внутренний неуправляемый объект из модуля <code>OpenCL</code>.<br />
Если вам не пришлось, по какой-либо причине, использовать <code>OpenCLABC</code> и <code>OpenCL</code> вместе - это свойство может понадобится только для дебага.</p>
</li>
<li><p>Свойство <code>.Properties</code>, возвращающее объект свойств внутреннего объекта.<br />
Свойства неуправляемого объекта никак не обрабатываются и не имеют описаний.
Но типы этих свойств всё равно преобразуются в управляемые (особо заметно на строках и массивах).</p>
</li>
</ul>
<hr />
<h1>Страницы:</h1>
<ul>
<li><a path="Platform/">		Platform	</a> - Платформа, объединяющая несколько совместимых устройств;</li>
<li><a path="Device/">		Device		</a> - Устройство, поддерживающее OpenCL;</li>
<li><a path="Context/">		Context		</a> - Контекст выполнения;</li>
<li><a path="Память/">		Память		</a> - Обёртки памяти GPU;</li>
<li><a path="Kernel/">		Kernel		</a> - Подпрограмма, выполняемая на GPU;</li>
<li><a path="ProgramCode/">	ProgramCode	</a> - Контейнер kernel'ов.</li>
</ul>
</div>
<script>on_start_folder("Простые обёртки", document.getElementById("page-3"))</script>
<div id="page-4" page_name="" hidden=true>
<p>В списке устройств контекста могут быть только совместимые друг с другом устройства.<br />
Коллекция совместимых устройств называется платформой и хранится в объектах типа <code>Platform</code>.</p>
<hr />
<p>Обычно платформы получают из статического свойства <code>Platform.All</code>:</p>
<pre><code>## uses OpenCLABC;
var pls := Platform.All;
pls.PrintLines;
</code></pre>
</div>
<script>on_start_folder("Platform", document.getElementById("page-4"))</script>
<script>on_end_folder()</script>
<div id="page-5" page_name="" hidden=true>
<p>Обычно устройства получают статическим методом <code>Device.GetAllFor</code>:</p>
<pre><code>## uses OpenCLABC;

foreach var pl in Platform.All do
begin
  Writeln(pl);
  var dvcs := Device.GetAllFor(pl, DeviceType.DEVICE_TYPE_ALL);
  if dvcs&lt;&gt;nil then dvcs.PrintLines;
  Writeln('='*30);
end;
</code></pre>
<p>И в большинстве случаев - это всё что вам понадобится.</p>
<hr />
<p>Но если где то нужен более тонкий контроль - можно создать несколько виртуальных
под-устройств, каждому из которых даётся часть ядер изначального устройства.<br />
Для этого используются методы <code>.Split*</code>:</p>
<pre><code>## uses OpenCLABC;

var dvc := Context.Default.MainDevice;

Writeln('Поддерживаемые типы .Spilt-ов:');
var partition_properties := dvc.Properties.PartitionProperties;
if (partition_properties.Length=0) or (partition_properties[0].val = System.IntPtr.Zero) then
begin
  Writeln('Ничего не поддерживается...');
  exit;
end else
  partition_properties.PrintLines;
Writeln('='*30);

Writeln('Виртуальные устройства, по 1 ядру каждое:');
if dvc.CanSplitEqually then
  // Если упадёт потому что слишком много
  // устройств - пожалуйста, напишите в issue
  dvc.SplitEqually(1).PrintLines else
  Writeln('Не поддерживается...');
Writeln('='*30);

Writeln('Два устройства, 1 и 2 ядра соответственно:');
if dvc.CanSplitByCounts then
  dvc.SplitByCounts(1,2).PrintLines else
  Writeln('Не поддерживается...');
Writeln('='*30);

</code></pre>
</div>
<script>on_start_folder("Device", document.getElementById("page-5"))</script>
<script>on_end_folder()</script>
<div id="page-6" page_name="" hidden=true>
<p>Контекст (объект типа <code>Context</code>) содержит информацию об устройствах,
на которых надо выделяет области памяти <code>OpenCL</code> и выполнять код для GPU.</p>
<hr />
<p>Создать контекст можно конструктором (<code>new Context(...)</code>).<br />
Контекст можно и не создавать, используя везде свойство <code>Context.Default</code>.</p>
<p>Если в операции, требующей контекст (как выделение памяти GPU),
его не указать - будет автоматически выбран <code>Context.Default</code>.<br />
Неявные очереди всегда используют <code>Context.Default</code>.</p>
<p>Изначально этому свойству присваивается контекст, использующий GPU, если оно имеется,
или любое другое устройство, поддерживающее OpenCL, если GPU отсутствует.</p>
<p>Если устройств поддерживающих <code>OpenCL</code> нет - <code>Context.Default</code> будет <code>nil</code>.<br />
Однако такая ситуация на практике невозможна, потому что OpenCL поддерживается
практически всеми современными устройствами, занимающимися выводом изображения на экран.<br />
Если <code>Context.Default = nil</code> - переустановите графические драйверы.</p>
<hr />
<p><code>Context.Default</code> можно перезаписывать.<br />
Используйте эту возможность только если во всей программе нужен общий контекст, но не стандартный.</p>
<p>Если ваша программа достаточно сложная чтобы нуждаться в нескольких контекстах - лучше не использовать
<code>Context.Default</code>. И присвоить ему <code>nil</code>, чтобы не использовать случайно (к примеру, неявной очередью).</p>
</div>
<script>on_start_folder("Context", document.getElementById("page-6"))</script>
<script>on_end_folder()</script>
<div id="page-7" page_name="" hidden=true>
<p>Программам, выпоняемые на GPU, не удобно использовать оперативную память.
Поэтому обычно лучше выделять память на самом GPU.</p>
<h1>Виды памяти в OpenCLABC:</h1>
<ul>
<li><a path="CLArray">	   	    CLArray		    </a> - Память GPU в форме массива записей.</li>
<li><a path="MemorySegment">		MemorySegment	</a> - Безформенная область памяти GPU;</li>
<li><a path="MemorySubSegment">	MemorySubSegment</a> - Виртуальная область памяти внутри MemorySegment;</li>
<li><a path="NativeValue">		NativeValue		</a> - Неуправляемая память RAM в форме одной записи;</li>
</ul>
</div>
<script>on_start_folder("Память", document.getElementById("page-7"))</script>
<div id="page-8" page_name="CLArray" hidden=true>
<p>Всё преимущество GPU над CPU состоит в выполнении одинаковой программы на рое из процессорных ядер.<br />
Каждое из этих ядер значительно медленнее ядер CPU, но используя весь рой вместе можно выполнять вычисления значительно быстрее.<br />
Но раз все ядра GPU выполняют одну и ту же программу - им обычно приходится передавать массив из однотипных данных.</p>
<p>Объект типа <code>CLArray&lt;T&gt;</code> (где <code>T</code> - почти любая запись) является массивом, содержимое которого хранится в памяти GPU, для более быстрого доступа из кода на GPU.</p>
<hr />
<p><code>CLArray&lt;T&gt;</code> создаётся конструктором:</p>
<pre><code>## uses OpenCLABC;

// Массив для 10 элементов типа integer
var a1 := new CLArray&lt;integer&gt;(Context.Default, 10);
// Заполняем нулями, потому что при создании CLArray может содержать мусорные данные
a1.FillValue(0);
a1.GetArray.Println;

// Массив, в который при создании копируются данные из переданного массива
var a2 := new CLArray&lt;byte&gt;(Context.Default, new byte[](1,2,3,4,5,6,7));
a2.GetArray.Println;

// Массив, в который при создании копируется часть данных из переданного массива
// Точнее, копируется 2 элемента, начиная с элемента с индексом 3
var cpu_array := |0,1,2,3,4,5,6,7|;
var a3 := new CLArray&lt;integer&gt;(Context.Default, 3, 2, cpu_array);
a3.GetArray.Println;
</code></pre>
<p>Кроме того, объект контекста можно не передавать, тогда для выделения памяти будет автоматически выбран <code>Context.Default</code>.</p>
<hr />
<p>Элементы этого массива, так же как у обычных массивов, можно читать через индексное свойство:</p>
<pre><code>## uses OpenCLABC;
var a := new CLArray&lt;integer&gt;(Context.Default, |0,1,4,9,16|);
a[3].Println;
a[4] := 5;
a[4].Println;
</code></pre>
<p>Но имейте в виду - каждое обращение к индексному свойству - это новое выполнение неявной очереди, поэтому заполнять или читать такой массив по 1 элементу не эффективно.</p>
<p>Лучше всего использовать полноценные очереди. Но если вы выполняете какое-то одноразовое заполнение - можно, хотя бы, использовать свойство <code>.Section</code>:</p>
<pre><code>## uses OpenCLABC;
var a := new CLArray&lt;integer&gt;(Context.Default, |0,1,4,9,16|);
// Заполняем элементы с индексами от 2 до 3 массивом |-1,-2|
a.Section[2..3] := |-1,-2|;
// Читаем и выводим элементы с 0 по 2 индексы как массив
a.Section[0..2].Println;
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-8"))</script>
<div id="page-9" page_name="MemorySegment" hidden=true>
<p>Некоторые данные, передаваемые в программы на GPU, могут быть общими (для всех ядер) или просто не однотипными.<br />
Такие данные можно хранить в типе <code>MemorySegment</code>, представляющем область памяти GPU, для которой не определено ничего кроме размера.</p>
<p>Область памяти <code>MemorySegment</code> создаётся конструктором (<code>new MemorySegment(...)</code>).<br />
Основные конструкторы принимают размер области памяти и, опционально, контекст. Если контекст не указывается - память выделяется на <code>Context.Default</code>.</p>
<p>Далее, в неё можно записывать и читать данные практически любого типа записи, в любой точке:</p>
<pre><code>## uses OpenCLABC;

var s := new MemorySegment(sizeof(int64));
// $ Это запись числа в 16-ичной СС
s.WriteValue&amp;&lt;int64&gt;($FEDCBA9876543210);
// .ToString('X') преобразовывает число в строку в 16-ичной СС
s.GetValue&amp;&lt;int64&gt;.ToString('X').Println;
// Можно читать кусок данных одного типа, как данные другого типа
s.GetValue&amp;&lt;integer&gt;.ToString('X').Println;
// Читаем начиная с четвёртого байта
// Я сделал 16-ичный вывод чтобы было видно, изначальное число разрезало пополам
s.GetValue&amp;&lt;integer&gt;(4).ToString('X').Println;
// Ну и никто не запретит прочитать как совершенно не совместимый тип данных
// Но в ответ вы получите мусорные данные
s.GetValue&amp;&lt;real&gt;.Println;
</code></pre>
<p>То есть, вам самим придётся заботится о типе внутренних данных, работая с <code>MemorySegment</code>, но взамен вы полностью контролируете содержимое области памяти.</p>
<hr />
<p>Память на GPU можно моментально освободить, вызвав метод <code>MemorySegment.Dispose</code>.<br />
Но вызывать его не обязательно, потому что этот метод вызывается автоматически во время сборки мусора.</p>
</div>
<script>on_page_added(document.getElementById("page-9"))</script>
<div id="page-10" page_name="MemorySubSegment" hidden=true>
<p>Но кроме выделения памяти на GPU - OpenCL так же позволяет выделять память внутри другой области памяти.<br />
Для этого используется тип <code>MemorySubSegment</code>:</p>
<pre><code>## uses OpenCLABC;

var c := Context.Default;

// Не обязательно MainDevice, можно взять любое устройство из контекста
var align := c.MainDevice.Properties.MemBaseAddrAlign;

var s := new MemorySegment(align*2, c);
// size может быть любым, но origin
// должно быть align*N, где N - целое
var s1 := new MemorySubSegment(s, 0, Min(123,align));
var s2 := new MemorySubSegment(s, align, align);

Writeln(s1);
Writeln(s2);
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-10"))</script>
<div id="page-11" page_name="NativeValue" hidden=true>
<p>OpenCL это неуправляемая библиотека. Обычно можно заставить управляеммые типы данных работать с ней.<br />
Но часто это приводит к дополнительным затратам производительности.<br />
И обычно не значит всегда - <code>MemorySegment.ReadValue</code>, принимающее запись <code>var</code>-параметром не может быть безопасным из за сборщика мусора
(и поэтому отсутствует).</p>
<p>Более прямым будет передача неуправляемых типов - указателей - без преобразований в подпрограммы модуля <code>OpenCL</code>.<br />
И эта возможность тоже существует, к примеру в виде <code>MemorySegment.WriteData</code>. Но такие указатели ещё более не_безопасны:<br />
Как минимум они требуют освобождения в <code>try-finally</code> чтобы избежать утечек памяти.
И защиты от дурака, не возволяющей записать значение типа <code>real</code> туда, где хранится <code>int64</code> - не существует.</p>
<p>Как что-то среднее между этими двумя вариантами - существует <code>NativeValue&lt;T&gt;</code>:<br />
Этот класс является обёрткой указателя на область памяти RAM.</p>
<pre><code>## uses OpenCLABC;

// В конструктор необходимо передавать значение,
// потому что иначе неуправляемая память будет содержать мусор
// Но можно передать default, чтобы заполнить выделяемую память нулями
var nv := new NativeValue&lt;integer&gt;(default(integer));

nv.Value := 5; // Перезаписываем значение,
nv.Value.Println; // И читаем назад

// Напрямую получать доступ к области памяти,
// через свойство Pointer, не рекомендуется
Writeln(nv.Pointer);
// Кроме как передавать nv.Pointer^ var-параметром

nv.Dispose; // Освобождение памяти - вызывается и само при сборке мусора
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-11"))</script>
<script>on_end_folder()</script>
<div id="page-12" page_name="" hidden=true>
<p>Объект типа <code>Kernel</code> представляет одну подпрограмму в OpenCL-C коде,
объявленную с ключевым словом <code>__kernel</code>.</p>
<hr />
<p>Обычно <code>Kernel</code> создаётся через индексное свойтсво <code>ProgramCode</code>:</p>
<pre><code>var code: ProgramCode;
...
var k := code['KernelName'];
</code></pre>
<p>Тут <code>'KernelName'</code> — имя подпрограммы-kernel'а в исходном коде (регистр важен!).</p>
<hr />
<p>Так же можно получить список всех kernel'ов объекта <code>ProgramCode</code>, методом <code>ProgramCode.GetAllKernels</code>:</p>
<pre><code>var code: ProgramCode;
...
var ks := code.GetAllKernels;
ks.PrintLines;
</code></pre>
</div>
<script>on_start_folder("Kernel", document.getElementById("page-12"))</script>
<div id="page-13" page_name="KernelArg" hidden=true>
<p>Методы, запускающие <code>Kernel</code> принимают специальные аргументы типа <code>KernelArg</code>, которые передаются в OpenCL-C код.<br />
Экземпляр <code>KernelArg</code> может быть создан из нескольких типов значений, а точнее:</p>
<pre><code>## uses OpenCLABC;

var k: Kernel;
var val1 := 3;
var val2 := 5;
var val3 := new NativeValue&lt;byte&gt;(7);
var a: array of byte;

k.Exec1(1,
  // Передавать можно:
  
  // Области памяти
  new CLArray&lt;byte&gt;(1),
  new MemorySegment(1),
  // Очереди возвращающие область памяти
  HFQ(()-&gt;new MemorySegment(1)),
  // В том числе CLArrayCCQ и т.п.
  CLArray&amp;&lt;byte&gt;.Create(1).NewQueue,
  
  // Указатель на размерное значение
  // (в kernel попадёт само значение, не указатель)
  @val2,
  // Так нельзя, потому что val1 была захвачена лямбдой из HFQ ниже
//  @val1,
  // Расширенный набор параметров для передачи адреса
  KernelArg.FromData(new System.IntPtr(@val2), new System.UIntPtr(sizeof(integer))),
  
  // Размерное значение
  val1,
  HFQ(()-&gt;val1),
  
  // В том числе неуправляемое
  val3,
  HFQ(()-&gt;val3),
  
  // Массив размерных значений
  a,
  HFQ(()-&gt;a)
  
);
</code></pre>
<p>Обратите внимание, <code>KernelArg</code> из указателя на <code>val2</code> будет немного эффективнее
чем <code>KernelArg</code> из самого значения <code>val2</code>. Но эту возможность стоит использовать
только как тонкую оптимизацию, потому что много чего может пойти не так.
Если передавать <code>@val2</code> в качестве <code>KernelArg</code> - надо знать все тонкости.</p>
<hr />
<p>Если <code>@x</code> передали вкачестве <code>KernelArg</code>:</p>
<ol>
<li><p><code>x</code> не может быть глобальной переменной или полем класса:</p>
<pre><code>uses OpenCLABC;

type
  t1 = class
    val1 := 1; // Не подходит, потому что поле класса
    static val2 := 2; // И статических полей это тоже касается
  end;

var
  val3 := 3; // Глобальные переменные - тоже статические поля
  k: Kernel; // k не важно где объявлена

procedure p1;
// Теоретически подходит, но вообще это плохой стиль кода
var val4 := 4;
begin

  // Однозначно подходит, потому что объявлена не только в той же
  // подпрограмме, но и прямо перед использованием в k.Exec*
  var val5 := 5;

  k.Exec1(1,

    // Это единственные 2 переменные, которые можно передавать адресом
    @val4,
    @val5

  );

end;

begin end.
</code></pre>
</li>
<li><p><code>x</code> не должно быть захвачего лямбдой.<br />
Хотя указатель на <code>x</code> уже можно захватывать:</p>
<pre><code>## uses OpenCLABC;

var k: Kernel;
var val1 := 3;

// На val1 всё ещё накладываются все ограничения,
// когда val1_ptr использована в качестве KernelArg
// Но к самой val1_ptr эти ограничения не применяются
var val1_ptr := @val1;

k.Exec1(1,

  val1,
  HFQ(()-&gt;val1_ptr^), // Захватили переменную val1_ptr, а не val1

  // val1 нигде не захвачена, поэтому теперь так можно
  @val1,
  val1_ptr // То же самое

);
</code></pre>
</li>
<li><p>Выходить из подпрограммы, где объявили <code>x</code> нельзя, пока <code>.Exec</code> не закончит выполнятся.
Это так же значит, что возвращать очередь, содержащую <code>KernelArg</code> из <code>@x</code> обычно нельзя.<br />
Но это можно обойти, если объявлять переменную в другой подпрограмме:</p>
<pre><code>uses OpenCLABC;

var k: Kernel; // Вообще лучше передавать параметром в p2

function p2(var val: integer): CommandQueueBase;
begin

  Result := k.NewQueue.AddExec1(1,

    @val

  );

end;

procedure p1;
begin
  var val: integer;

  var q := p2(val);
  // Опять же, q не должна продолжать выпоняться
  // после выхода из p1, потому что тут объявлена val
  Context.Default.SyncInvoke(q);

end;

begin
  p1;
end.
</code></pre>
<p>Тут <code>val</code> объявлена в <code>p1</code>. При этом <code>val</code> в <code>p2</code> является синонимом <code>val</code> из <code>p1</code>,
потому что объявлена <code>var</code>-параметром - передачей по ссылке.<br />
Если передать без <code>var</code> перед параметром - <code>val</code> из <code>p2</code> будет копией,
а значит перестанет существовать после выхода из <code>p2</code>.</p>
</li>
</ol>
<p>Обратите внимание: Компилятор не заставит вас следовать описанным тут ограничениям.
И показанные на этой странице коды могут работать не смотря на эти ограничения,
потому что саму область памяти, на которую ссылается созданный <code>KernelArg</code>, никто никогда не уничтожает.</p>
<p>Но в то же время эту же область памяти может использовать под другие данные, таким образом
заменяя значение которое вы пытались передать в GPU на мусорные данные.</p>
<p>Это плохо в первую очередь потому, что на перезапись этих данных может повлиять совершенно не связанная
часть кода, или различие архитектур компьтеров, таким образом усложняя поиск источника ошибки.</p>
<p>Поэтому, ещё раз, используйте передачу адреса в качестве <code>KernelArg</code> только как тонкую оптимизацию и только когда понимаете что делаете.</p>
</div>
<script>on_page_added(document.getElementById("page-13"))</script>
<script>on_end_folder()</script>
<div id="page-14" page_name="" hidden=true>
<p>Обычные программы невозможно запустить на GPU. Для этого надо писать особые программы.<br />
В контексте OpenCL - эти программы обычно пишутся на языке &quot;OpenCL C&quot; (основанном на языке &quot;C&quot;).</p>
<p>Язык OpenCL-C это часть библиотеки OpenCL, поэтому его справку можно найти <a href="https://www.khronos.org/registry/OpenCL/">там же</a>, где и справку OpenCL.</p>
<p>В <code>OpenCLABC</code> код на языке &quot;OpenCL C&quot; хранится в объектах типа <code>ProgramCode</code>.<br />
Объекты этого типа используются только как контейнеры.
Один объект ProgramCode может содержать любое количествово подпрограмм-kernel'ов.</p>
<hr />
<h1>Есть 2 способа создать объект типа <code>ProgramCode</code>:</h1>
<ul>
<li><p><a path="Создание из исходного кода">		Из исходного кода			</a></p>
</li>
<li><p><a path="Создание из бинарного файла">	Из прекомпилированного кода	</a></p>
</li>
</ul>
</div>
<script>on_start_folder("ProgramCode", document.getElementById("page-14"))</script>
<div id="page-15" page_name="Создание из исходного кода" hidden=true>
<p>Конструктор <code>ProgramCode</code> (<code>new ProgramCode(...)</code>) принимает тексты исходников программы на языке OpenCL-C.<br />
<strong>Именно тексты исходников, не имена файлов!</strong></p>
<p>Так же, как исходники паскаля хранят в .pas файлах, исходники OpenCL-C кода хранят в .cl файлах.
Но вообще, это не принципиально, потому что код даже не обязательно должен быть в файле.</p>
<p>Так как конструктор <code>ProgramCode</code> принимает текст - исходники программы на OpenCL-C можно хранить даже в
строке в .pas программе. Тем не менее, храненить исходники OpenCL-C кода в .cl файлах обычно удобнее всего.</p>
</div>
<script>on_page_added(document.getElementById("page-15"))</script>
<div id="page-16" page_name="Создание из бинарного файла" hidden=true>
<p>После создания объекта типа <code>ProgramCode</code> из исходников можно вызвать
метод <code>ProgramCode.SerializeTo</code>, чтобы сохранить код в бинарном и прекомпилированном виде.
Обычно это делается отдельной программой (не той же самой, которая будет использовать этот бинарный код).</p>
<p>После этого основная программа может создать объект <code>ProgramCode</code>,
используя статический метод <code>ProgramCode.DeserializeFrom</code>.</p>
<p>Пример можно найти в папке <code>Прекомпиляция ProgramCode</code> или <a href="https://github.com/SunSerega/POCGL/tree/master/Samples/OpenCLABC/%D0%9F%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%86%D0%B8%D1%8F%20ProgramCode">тут</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-16"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
<div id="page-17" page_name="" hidden=true>
<p>Передавать команды для GPU по одной не эффективно.
Гораздо эффективнее передавать несколько команд сразу.</p>
<p>Для этого существуют очереди (типы, наследующие от <code>CommandQueueBase</code>).
Они хранят произвольное количество команд для GPU.
А при необходимости также и части кода, выполняемые на CPU.</p>
<hr />
<h1>Страницы:</h1>
<ul>
<li><p><a path="Возвращаемое значение очередей/">	Возвращаемое значение	</a></p>
</li>
<li><p><a path="Выполнение очередей/">				Выполнение				</a></p>
</li>
<li><p><a path="Создание очередей/">					Создание				</a></p>
</li>
<li><p><a path="Вложенные очереди/">					Вложенные очереди		</a></p>
</li>
</ul>
</div>
<script>on_start_folder("Очередь [команд] (CommandQueue)", document.getElementById("page-17"))</script>
<div id="page-18" page_name="" hidden=true>
<p>Все типы очередей наследует от <code>CommandQueueBase</code>. Это значит что любую очередь можно сохранить в переменную типа <code>CommandQueueBase</code>.<br />
Но о значении типа <code>CommandQueueBase</code> известно не на много больше чем о значении типа <code>object</code>.</p>
<p>Так же, все очереди наследуют от одного из двух типов:</p>
<ol>
<li><code>CommandQueueNil</code> - очередь возващающая <code>nil</code> (именно нулевую ссылку, не пустое значение любого типа).</li>
<li><code>CommandQueue&lt;T&gt;</code> (где <code>T</code> - любой тип) - очередь возвращающая значение типа <code>T</code>;</li>
</ol>
<p>После выполнения очереди <code>CommandQueue&lt;T&gt;</code> метод <code>Context.SyncInvoke</code> возвращает то, что вернула очередь.<br />
А если использовать метод <code>Context.BeginInvoke</code> - возвращаемое значение можно получить с помощью метода <code>CLTask&lt;T&gt;.WaitRes</code>.</p>
<hr />
<p>Результат других типов очередей нельзя получить, но их можно преобразовать к <code>CommandQueue&lt;T&gt;</code> с произвольным <code>T</code> с помощью <code>.Cast</code>:</p>
<pre><code>## uses OpenCLABC;

// Q объявлена как CommandQueueBase,
// а значит в неё можно сохранить любую очередь
var Q: CommandQueueBase;

// В данном случае сохраняем CommandQueueNil
Q := HPQ(()-&gt;Writeln('Q выполнилась'));

// Преобразовывать nil можно в любой ссылочный тип
Writeln(Context.Default.SyncInvoke( Q.Cast&amp;&lt;object&gt; ));
// Exception тоже класс - поэтому можно и в него
// Но в результате всё равно получится nil
Writeln(Context.Default.SyncInvoke( Q.Cast&amp;&lt;Exception&gt; ));

Sleep(1000); // Чтобы было видно предыдущий вывод
//Ошибка времени выполнения: .Cast не может преобразовывать nil в integer
// Ошибка кидается ещё в момент создания .Cast очереди
Writeln(Context.Default.SyncInvoke( Q.Cast&amp;&lt;integer&gt; ));
</code></pre>
<p>Подробнее <a path="../Создание очередей/С кодом для CPU">тут</a>.</p>
<hr />
<p>В то же время, если результат выполнения очереди не нужен,
от него можно избавится с помощью <code>.DiscardResult</code>.</p>
<p>Это может понадобится если вы хотите:</p>
<ul>
<li>Защититься от случайного использования результата;</li>
<li>Передать очередь туда, где ожидается <code>CommandQueueNil</code>;</li>
<li>Сократить (мизерное, но не нулевое) использование ресурсов, выделяемых для передачи резутата. К примеру:</li>
</ul>
<pre><code>## uses OpenCLABC;

var Q := HFQQ(()-&gt;
begin
  Writeln('Функция выполнилась');
  Result := 5;
end).DiscardResult;

var t := Context.Default.BeginInvoke(Q as CommandQueueBase);
$'Has result: {not (t is CLTaskNil)}'.Println;

t.Wait;
</code></pre>
<p>Без <code>.DiscardResult</code> - <code>BeginInvoke</code> создаст <code>CLTask&lt;integer&gt;</code>,
не смотря на то, что в него передают <code>CommandQueueBase</code>.</p>
<p>А с <code>.DiscardResult</code> - <code>HFQQ</code> выполняется как будто это <code>HPQQ</code>.</p>
<p>Обратите внимание, если результат очереди выкидывается, без
какого-либо использования - ресурсы на него не выделяются. К примеру:</p>
<pre><code>## uses OpenCLABC;

Context.Default.SyncInvoke(
  HFQQ(()-&gt;1) +
  HFQQ(()-&gt;2)
);
</code></pre>
<p>Первая <code>HFQQ</code> выполнится как будто это <code>HPQQ</code>, потому что
из <code>SyncInvoke</code> можно получить только результат второй <code>HFQQ</code>.</p>
<hr />
<p>Очереди, созданные из областей памяти OpenCL или kernel'ов возващают свои области памяти/<code>Kernel</code>'ы соответственно, из которых были созданы;<br />
Очереди, созданные с <code>HF[Q]Q</code> - значение, которое вернёт переданная функция;<br />
Очереди, созданные с <code>HP[Q]Q</code> являются <code>CommandQueueNil</code>.</p>
<p>Демонстрация:</p>
<pre><code>## uses OpenCLABC;

/// Вывод типа и значения объекта
procedure OtpObject(o: object) :=
$'{if o=nil then nil else TypeName(o)}[{_ObjectToString(o)}]'.Println;
// _ObjectToString это функция, которую используют
// Writeln и Println для форматирования значений

procedure Test(q: CommandQueueBase) :=
OtpObject(Context.Default.SyncInvoke(
  // Преобразовываем результат к object, чтобы его вернула SyncInvoke
  q.Cast&amp;&lt;object&gt;
));

var s := new MemorySegment(1);

// Тип - MemorySegment, потому что очередь создали из него
Test( s.NewQueue );

// Тип - integer, потому что это тип по умолчанию для выражения (5)
Test( HFQQ(()-&gt;5) );

// Тип - string, по той же причине
Test( HFQQ(()-&gt;'abc') );

// Тип отсутствует, потому что HP[Q]Q возвращает nil
Test( HPQQ(()-&gt;Print('Выполнилась HPQQ:')) );
</code></pre>
<hr />
<p>Проверить что очередь ничего не возвращает очень просто:</p>
<pre><code>var Q: CommandQueueBase;
...
if Q is CommandQueueNil(var cqn) then
  p1(cqn) else
  p2(Q);
</code></pre>
<p>Но для типа <code>CommandQueue&lt;T&gt;</code> надо указать конкретный тип, чтобы вызвать <code>is</code>.
Другими словами, с помощью <code>is</code> можно проверять только по одному типу возвращаемого значения за раз:</p>
<pre><code>var Q: CommandQueueBase;
...
if Q is CommandQueueNil(var cqn) then
  p1(cqn) else
if Q is CommandQueue&lt;byte&gt;(var cq) then
  p2&amp;&lt;byte&gt;(cq) else
if Q is CommandQueue&lt;word&gt;(var cq) then
  p2&amp;&lt;word&gt;(cq) else
  // Не должно происходить
  raise new System.NotSupportedException;
</code></pre>
<p>Если надо вызвать <code>p2</code> для очереди с любым возвращаемым значением - используется <code>.UseTyped</code>:</p>
<pre><code>uses OpenCLABC;

procedure p1(cq: CommandQueueNil) := Writeln('nil');
procedure p2&lt;T&gt;(cq: CommandQueue&lt;T&gt;) := Writeln($'&lt;{TypeToTypeName(typeof(T))}&gt;');

type
  // Не обязательно запись
  TypedUser = record(ITypedCQUser)
    
    public procedure UseNil(cq: CommandQueueNil) := p1(cq);
    public procedure Use&lt;T&gt;(cq: CommandQueue&lt;T&gt;) := p2(cq);
    
  end;
  
procedure Test(Q: CommandQueueBase) :=
Q.UseTyped(new TypedUser);

begin
  Test(HPQ(()-&gt;begin end));
  Test(HFQ(()-&gt;0));
  Test(HFQ(()-&gt;0.0));
end.
</code></pre>
<p>Объявлять дополнительный тип (<code>TypedUser</code> в этом коде) необходимо потому, что иначе
передать подпрограмму <code>Use&lt;T&gt;</code>, не указывая её <code>&lt;T&gt;</code>, в <code>UseTyped</code> не получится.</p>
<p>Так же, если нужно не только использовать очередь, но и что-то вернуть - используется <code>.ConvertTyped</code>:</p>
<pre><code>uses OpenCLABC;

type
  // Получает имя типа результата очереди, или nil если он отсутствует
  QueueConverterResTName = record(ITypedCQConverter&lt;string&gt;)
    
    public function ConvertNil(cq: CommandQueueNil): string := nil;
    public function Convert&lt;T&gt;(cq: CommandQueue&lt;T&gt;): string := TypeToTypeName(typeof(T));
    
  end;
  
procedure Test(Q: CommandQueueBase) :=
Writeln( Q.ConvertTyped(new QueueConverterResTName) );

begin
  Test(HPQ(()-&gt;begin end));
  Test(HFQ(()-&gt;0));
  Test(HFQ(()-&gt;0.0));
end.
</code></pre>
<p>И <code>.UseTyped</code> и <code>.ConvertTyped</code> гарантируют что обязательно будет вызван ровно один
из двух методов - либо принимающий <code>CommandQueueNil</code>, либо принимающий <code>CommandQueue&lt;T&gt;</code>.</p>
</div>
<script>on_start_folder("Возвращаемое значение очередей", document.getElementById("page-18"))</script>
<script>on_end_folder()</script>
<div id="page-19" page_name="" hidden=true>
<p>Самый простой способ выполнить очередь - вызвать метод <code>Context.SyncInvoke</code>.</p>
<p>У него есть три перегрузки, для <code>CommandQueueBase</code>, <code>CommandQueueNil</code> и <code>CommandQueue&lt;T&gt;</code>.
Только последняя возвращает результат.</p>
<p>Но если надо выполнить очередь асинхронно - лучше использовать метод <code>Context.BeginInvoke</code>,
потому что его всё равно вызывает <code>Context.SyncInvoke</code>.</p>
<p><code>Context.BeginInvoke</code> запускает асинхронное выполнение очереди.
Как только очередь была полностью запущена он возвращает объект типа
<code>CLTaskBase</code>, <code>CLTaskNil</code> или <code>CLTask&lt;T&gt;</code> для соответствующих типов очередей.</p>
<p>Так же как в случае очередей, <code>CLTaskNil</code> и <code>CLTask&lt;T&gt;</code> наследуют от <code>CLTaskBase</code>.<br />
У всех <code>CLTask</code>-ов есть:</p>
<ul>
<li>Свойства <code>.OrgContext</code> и <code>.OrgQueue</code>, возвращающие контекст выполнения и выполняемую очередь соответственно.</li>
<li>Метод <code>.Wait</code> для ожидания окончания выполнения очереди.</li>
</ul>
<p>У <code>CLTask&lt;T&gt;</code> так же есть метод <code>.WaitRes</code>, вызывающий <code>.Wait</code> и затем возвращающий результат очереди.</p>
<hr />
<p>При выполнении очереди может произойти несколько исключений, поэтому, чтобы получить можно было все, их упаковывает в <code>System.AggregateException</code>:</p>
<pre><code>## uses OpenCLABC;

try
  
  // Context.SyncInvoke или CLTask.Wait
  Context.Default.SyncInvoke(Q);
  
except
  on e: System.AggregateException do
    e.InnerExceptions.PrintLines;
end;
</code></pre>
<p>Но для более тонкого контроля лучше использовать
<a path="../Создание очередей/Обработка исключений"><code>.Handle*</code> методы очередей</a>.</p>
</div>
<script>on_start_folder("Выполнение очередей", document.getElementById("page-19"))</script>
<script>on_end_folder()</script>
<div id="page-20" page_name="" hidden=true>
<h1>Есть всего 10 базовых способов создать очередь:</h1>
<ol>
<li><p><a path="Из объекта с коммандами">				Из объекта с коммандами					</a></p>
</li>
<li><p><a path="Из готового результата">				Из готового результата					</a></p>
</li>
<li><p><a path="С кодом для CPU">						Из обычной подпрограммы					</a></p>
</li>
<li><p><a path="Комбинируя другие очереди">				Из нескольких других очередей			</a></p>
</li>
<li><p><a path="Из повторения очереди">					Из повторения одной очереди				</a></p>
</li>
<li><p><a path="Множественное использование очереди">	Несколько очередей с общим результатом	</a></p>
</li>
<li><p><a path="Общие методы CCQ объектов">				Общими методами CCQ объектов			</a></p>
</li>
<li><p><a path="Из ожидания очередей">					Из ожидания других очередей				</a></p>
</li>
<li><p><a path="Обработка исключений">					Из очереди + обработчика исключений		</a></p>
</li>
<li><p><a path="Не создавая явно">						Не пытаясь								</a></p>
</li>
</ol>
</div>
<script>on_start_folder("Создание очередей", document.getElementById("page-20"))</script>
<div id="page-21" page_name="Из объекта с коммандами" hidden=true>
<p>OpenCL используется для передачи комманд в GPU.
Поэтому в первую очередь стоит поговорить об очередях <code>OpenCLABC</code>, содержащих комманды для GPU.</p>
<p>Такие очереди создаются из некоторых типов-простых_обёрток методом <code>.NewQueue</code> и имеют тип с припиской <code>CCQ</code>.<br />
(&quot;Command Container Queue&quot;, то есть очередь-контейнер для коман GPU)<br />
К примеру, метод <code>CLArray&lt;byte&gt;.NewQueue</code> вернёт очередь типа <code>CLArrayCCQ&lt;byte&gt;</code>, наследующего от <code>CommandQueue&lt; CLArray&lt;byte&gt; &gt;</code>.</p>
<p>Чтобы создать саму комманду - надо вызвать соответствующий ей метод <code>CCQ</code> объекта. К примеру:</p>
<pre><code>## uses OpenCLABC;

// Массив на 3 элемента типа integer
var a := new CLArray&lt;integer&gt;(3);

// Создаём очередь
var q: CLArrayCCQ&lt;integer&gt; := a.NewQueue;

Context.Default.SyncInvoke(q
  .ThenWriteValue(1, 0)
  // Записывать по 1 элементу не эффективно
  // Лучше сначала создать массив в RAM
  // А затем послать его целиком
  .ThenWriteArray(|5,7|, 1, 2,0)
  // .ThenGet методы особенные, потому что не возвращают CCQ объект
  // В данном случае эта комманда читает весь CLArray как обычный массив в RAM
  .ThenGetArray
).Println;
</code></pre>
<p>Также, <code>CCQ</code> очереди можно создавать из очередей, возвращающих объект с командами. Для этого используется конструктор:</p>
<pre><code>var q0: CommandQueue&lt;MemorySegment&gt;;
...
var q := new MemorySegmentCCQ(q0);
</code></pre>
<hr />
<p>Команды объектов, представляющих память на GPU, можно разделить на группы.</p>
<p>По направлению передачи:</p>
<ol>
<li><code>Write</code> и <code>Fill</code>: Из RAM в память GPU;</li>
<li><code>Read</code> и <code>Get</code>: Из памяти GPU в RAM;</li>
<li><code>Copy</code>: Между двумя областями памяти GPU.</li>
</ol>
<p>И по типу данных на стороне RAM:</p>
<ol>
<li><code>Data</code>: Используются данные, находящиеся в RAM по указанному адресу;</li>
<li><code>Value</code>: Используется размерное значение;</li>
<li><code>Array</code>: Используется содержимое указанного массива размерных значений.</li>
</ol>
<p>Но при этом отсутствуют некоторые комбинации.</p>
<p>В первую очередь, в случае <code>Copy</code> нет понятия типа данных на стороне RAM, потому что RAM в принципе не задействуется.</p>
<p>И нет <code>GetData</code>, потому что в случае ошибок будут утечки памяти.
Вместо него выделяйте память явно и передавайте её в <code>ReadData</code>.</p>
<p>Так же, <code>WriteValue</code> может принимать размерное значение и <code>NativeValue</code>, но <code>ReadValue</code> принимает только <code>NativeValue</code>.<br />
Это потому, что принимать размерное значение в <code>ReadValue</code> <code>var</code>-параметром не безопасно,
как и в случае передачи адреса в качестве <a path="../../Простые обёртки/Kernel/KernelArg"> <code>KernelArg</code> </a>.<br />
Если вы понимаете что делаете - используйте <code>ReadData</code>, явно передавая в него адрес вашего значения (то есть указатель).<br />
Но обычно лучше использовать <code>GetValue</code>, создающее и возвращающее новое размерное значение, либо <code>ReadValue</code> принимающее <code>NativeValue</code>.</p>
<hr />
<p>Кроме таких объектов, методы-команды для GPU есть только у <code>Kernel</code>. И все они представляют запуск kernel'а.</p>
<hr />
<p>Комманда <code>.ThenGet</code> не выполняется, если её результат выкидывается.
Подробнее <a path="../Возвращаемое значение очередей/">тут</a>.</p>
<p>Это оптимизация, которую вы можете заметить только в одном случае:
Если параметры этой команды были неправильные - ошибка не возникнет.</p>
</div>
<script>on_page_added(document.getElementById("page-21"))</script>
<div id="page-22" page_name="Из готового результата" hidden=true>
<p>Константы в любом контексте позволяют проводить особые оптимизации.<br />
В случае <code>OpenCLABC</code> - константные очереди являются константами только на время выполнения очереди.</p>
<pre><code>## uses OpenCLABC;

var cq := new ConstQueue&lt;integer&gt;(1);
Context.Default.SyncInvoke(cq).Println;

cq.Value := 2;
Context.Default.SyncInvoke(cq).Println;
</code></pre>
<p>Обратите внимание, <code>.Value</code> нельзя изменять пока <code>.BeginInvoke</code> не досоздаст <code>CLTask</code>. Иначе поведение неопределено.</p>
<hr />
<p>Так же, константную очередь можно создать присвоив значение результата туда, где ожидается очередь:</p>
<pre><code>var q: CommandQueue&lt;integer&gt; := 1;
</code></pre>
<p>Для этого тип значения (<code>1</code>) и результата очереди <code>&lt;integer&gt;</code> должны полностью совпадать. К примеру так сделать не позволит:</p>
<pre><code>//Ошибка: Нельзя преобразовать тип integer к CommandQueue&lt;object&gt;
var q: CommandQueue&lt;object&gt; := 1;
</code></pre>
<p>Чтобы можно было присвоить - значение надо явно преобразовать к <code>object</code>:</p>
<pre><code>var q: CommandQueue&lt;object&gt; := object(1);
</code></pre>
<p>Этот способ создания константной очереди обычно используется подпрограммами, принимающими очереди:</p>
<pre><code>## uses OpenCLABC;

procedure p1(q: CommandQueue&lt;integer&gt;) :=
Context.Default.SyncInvoke(q).Println;

p1(1);
p1(HFQQ(()-&gt;2));
</code></pre>
<p>В <code>OpenCLABC</code> есть множество подпрограмм, принимающий очередь вместо значения.
<a path="../Вложенные очереди/">Подробнее</a>.</p>
<hr />
<p>Если нужно сохранить константную очередь в переменную, можно написать:</p>
<pre><code>var q1 := new ConstQueue&lt;integer&gt;(1);
var q2: CommandQueue&lt;integer&gt; := 3;
</code></pre>
<p>Но оба способа объёмны и, что важнее, требуют указать тип значения явно.</p>
<p>Поэтому в <code>OpenCLABC</code> есть вспомогательная подпрограмма:</p>
<pre><code>var q3 := CQ(3);
</code></pre>
<hr />
<p>У очередей есть возвращаемое значение, но чтобы передавать в них данные при запуске - можно
или создавать новую очередь на каждое выполнение (что очень не эффективно), или выкручиваться так:</p>
<pre><code>uses OpenCLABC;

type
  MyQueueContainer = sealed class
    private Q: CommandQueue&lt;integer&gt;;
    private par1 := new ConstQueue&lt;integer&gt;(-1);
    private par2 := new ConstQueue&lt;string&gt;(nil);
    
    // Эта очередь ничего полезного не делает, но это только пример
    public constructor := self.Q :=
    MemorySegment.Create(sizeof(integer)).NewQueue
    .ThenWriteValue( self.par1 )
    .ThenQueue( self.par2.ThenQuickUse(x-&gt;Writeln(x)) )
    .ThenGetValue&amp;&lt;integer&gt;;
    
    public function Invoke(par1: integer; par2: string): integer;
    begin
      var tsk: CLTask&lt;integer&gt;;
      // Нужна блокировка, чтобы если метод Invoke будет выполнен
      // в нескольких потоках одновременно, .Value параметров
      // не могло поменяться пока Context.BeginInvoke создаёт CLTask
      lock self do
      begin
        self.par1.Value := par1;
        self.par2.Value := par2;
        tsk := Context.Default.BeginInvoke(Q);
      end;
      
      Result := tsk.WaitRes;
    end;
    
  end;
  
begin
  var cont := new MyQueueContainer;
  
  cont.Invoke(1, 'abc').Println;
  cont.Invoke(2, 'def').Println;
  
end.
</code></pre>
<p>Но это не очень красиво. Можно сделать красивее, используя специальную очередь-параметр.<br />
<code>ParameterQueue&lt;T&gt;</code> тоже является константной во время выполнения очереди, но используется по-другому:</p>
<pre><code>uses OpenCLABC;

type
  MyQueueContainer = sealed class
    private Q: CommandQueue&lt;integer&gt;;
    private par1 := new ParameterQueue&lt;integer&gt;('par1');
    private par2 := new ParameterQueue&lt;string&gt;('par2');
    
    // Эта очередь ничего полезного не делает, но это только пример
    public constructor := self.Q :=
    MemorySegment.Create(sizeof(integer)).NewQueue
    .ThenWriteValue( self.par1 )
    .ThenQueue( self.par2.ThenQuickUse(x-&gt;Writeln(x)) )
    .ThenGetValue&amp;&lt;integer&gt;;
    
    public function Invoke(par1: integer; par2: string) :=
    Context.Default.SyncInvoke(self.Q,
      self.par1.NewSetter(par1),
      self.par2.NewSetter(par2)
    );
    
  end;
  
begin
  var cont := new MyQueueContainer;
  
  cont.Invoke(1, 'abc').Println;
  cont.Invoke(2, 'def').Println;
  
end.
</code></pre>
<p>Таким образом код получается значительно чище.<br />
Кроме того, при запуске очереди проводятся проверки, чтобы очереди-параметры
не оказывались не_определены или установлены дважды.</p>
</div>
<script>on_page_added(document.getElementById("page-22"))</script>
<div id="page-23" page_name="С кодом для CPU" hidden=true>
<p>Иногда между командами для GPU надо вставить выполнение обычного кода на CPU.
А разрывать для этого очередь на две части - плохо, потому что
одна целая очередь всегда выполнится быстрее двух её частей.</p>
<p>Поэтому существует множество типов очередей, хранящих обычный код для CPU.</p>
<hr />
<p>Чтобы создать самую простую такую очередь используются глобальные подпрограммы <code>HFQ</code> и <code>HPQ</code>:</p>
<p>HFQ — Host Function Queue<br />
HPQ — Host Procedure Queue<br />
(Хост в контексте OpenCL - это CPU, потому что с него посылаются команды для GPU)</p>
<p>Они возвращают очередь, выполняющую код (функцию/процедуру соотвественно) на CPU.<br />
Пример применения приведён <a path="../Возвращаемое значение очередей/"> на странице выше</a>.</p>
<hr />
<p>Так же бывает нужно использовать результат предыдущей очереди в коде на CPU.
Для этого используются методы <code>.ThenUse</code> и <code>.ThenConvert</code>:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQ(()-&gt;5);

Context.Default.SyncInvoke(Q
  .ThenUse(x-&gt;Println($'x*2 = {x*2}'))
  .ThenConvert(x-&gt;$'x^2 = {x**2}')
).Println;
</code></pre>
<p><code>.ThenUse</code> дублирует возвращаемое значение предыдущей очереди (<code>Q</code> в примере).<br />
А <code>.ThenConvert</code> возвращает результат выполнения переданной функции, как и <code>HFQ</code>.</p>
<hr />
<p><code>OpenCLABC</code> очереди существуют чтобы можно было удобно описывать параллельные процедуры.
Поэтому, с расчётом на параллельность, обычные очереди с кодом для CPU создают себе по одному потоку выполнения (<code>Thread</code>) при запуске.</p>
<p>Этот поток выполнения запускается до выхода из <code>Context.BeginInvoke</code> и остаётся в режиме ожидая.<br />
Но даже если игнорировать затраты на запуск потока, выход из режима ожидания это не моментальня операция.</p>
<p>Если надо выполнить очень простое действие, как в последнем примере выше, эти затраты неоправданны.<br />
Для таких случаев используется <code>Quick</code> версии очередей:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQQ(()-&gt;5);

Context.Default.SyncInvoke(Q
  .ThenQuickUse(x-&gt;Println($'x*2 = {x*2}'))
  .ThenQuickConvert(x-&gt;$'x^2 = {x**2}')
).Println;
</code></pre>
<p>В отличии от предыдущего примера, в данном будет создан только один поток выполнения (его всегда создаёт <code>Context.BeginInvoke</code>).</p>
<p>В общем случае <code>Quick</code> очереди стараются выполняться на одном из уже существующих потоков выполнения, но так чтобы не нарушать порядок выполнения очередей.</p>
<p>В случае <code>HFQ</code> и <code>HPQ</code>, их <code>Quick</code> варианты это <code>HFQQ</code> и <code>HPQQ</code> соответственно.</p>
<hr />
<p>Если вам необходимо быстро преобразовать тип возвращаемого значения очереди - можно использовать <code>.ThenQuickConvert</code>:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQQ(()-&gt;1 as object);

Context.Default.SyncInvoke(
  Q.ThenQuickConvert(x-&gt;integer(x))
).Println;
</code></pre>
<p>Но в <code>OpenCLABC</code> для случая простого преобразования существует особо-оптимизированный метод <code>.Cast</code>.<br />
Он ограничен примерно так же, как метод последовательностей <code>.Cast</code>. То есть:</p>
<pre><code>uses OpenCLABC;

type t1 = class end;
type t2 = class(t1) end;

begin
  var Q1: CommandQueue&lt;integer&gt; := 5;
  var Q2: CommandQueueBase := Q1;
  var Q3: CommandQueue&lt;t1&gt; := (new t2) as t1;
  var Q4: CommandQueue&lt;t1&gt; := new t1;
  var Q5: CommandQueue&lt;t2&gt; := new t2;
  
  // Можно, потому что к object можно преобразовать всё
  Context.Default.SyncInvoke( Q1.Cast&amp;&lt;object&gt; );
  
  //Ошибка: .Cast не может преобразовывать integer в byte
  // Преобразование записей меняет представление данных в памяти
  // Можно преобразовывать только object в запись и назад
//  Context.Default.SyncInvoke( Q1.Cast&amp;&lt;byte&gt; );
  
  // Можно, Q2 и так имеет тип CommandQueue&lt;integer&gt;,
  // а значит тут Cast вернёт (Q2 as CommandQueue&lt;integer&gt;)
  Context.Default.SyncInvoke( Q2.Cast&amp;&lt;integer&gt; );
  
  // Можно, потому что Q3 возвращает t2
  Context.Default.SyncInvoke( Q3.Cast&amp;&lt;t2&gt; );
  
  //Ошибка: Не удалось привести тип объекта &quot;t1&quot; к типу &quot;t2&quot;.
  // Q4 возвращает не t2 а именно t1
//  Context.Default.SyncInvoke( Q4.Cast&amp;&lt;t2&gt;.HandleDefaultRes(e-&gt;e.Message.Println&lt;&gt;nil, new t2) );
  
  // Можно, потому что t2 наследует от t1
  Context.Default.SyncInvoke( Q5.Cast&amp;&lt;t1&gt; );
  
end.
</code></pre>
<p>Кроме того, <code>.Cast</code> можно применять к очередям без типа результата:</p>
<pre><code>## uses OpenCLABC;

var Q1 := HPQQ(()-&gt;begin end);
var Q2 := HFQQ(()-&gt;5) as CommandQueueBase;

// .Cast применимо к CommandQueueNil, но результат будет всегда nil
Writeln( Context.Default.SyncInvoke(Q1.Cast&amp;&lt;object&gt;) );
// .Cast применимо и к CommandQueueBase
Writeln( Context.Default.SyncInvoke(Q2.Cast&amp;&lt;object&gt;) );
</code></pre>
<p>Ближайшей альтернативой к вызову <code>CommandQueueBase.Cast</code> будет вызов <code>.ConvertTyped</code> - но для этого надо на много больше кода.</p>
<hr />
<p>Основная оптимизация <code>.Cast</code> состоит в том, что преобразование не выполняется, если возможно.
Но будьте осторожны, в некоторых случаях такая оптимизация приглушит ошибку:</p>
<pre><code>uses OpenCLABC;

type t1 = class end;
type t2 = class(t1) end;

begin
  Context.Default.SyncInvoke( HFQ(()-&gt;new t1).Cast&amp;&lt;t2&gt;.Cast&amp;&lt;t1&gt; );
end.
</code></pre>
<p>Преобразование в <code>t2</code> не выполняется из-за следующего <code>.Cast</code>, поэтому
проверяется только преобразование <code>defalt(t1)</code> к <code>t2</code> (во время создания очереди) - а оно допустимое.</p>
</div>
<script>on_page_added(document.getElementById("page-23"))</script>
<div id="page-24" page_name="Комбинируя другие очереди" hidden=true>
<p>Если сложить две очереди A и B (<code>var C := A+B</code>) — получится очередь C, в которой сначала выполнится A, а затем B.<br />
Очередь C будет считаться выполненной тогда, когда выполнится очередь B.</p>
<p>Если умножить две очереди A и B (<code>var C := A*B</code>) — получится очередь C, в которой одновременно начнут выполняться A и B.<br />
Очередь C будет считаться выполненной тогда, когда обе очереди (A и B) выполнятся.</p>
<p>Как и в математике, умножение имеет бОльший приоритет чем сложение.</p>
<p>В обоих случаях очередь C будет возвращать то, что вернула очередь B. То есть если складывать и умножать много очередей - результат будет всегда возвращать то, что вернула самая последняя очередь.</p>
<p>Простейший пример:</p>
<pre><code>## uses OpenCLABC;

var q1 := HPQ(()-&gt;
begin
  // lock необходим чтобы при параллельном выполнении два потока
  // не пытались использовать вывод одновременно. Иначе выведет кашу
  lock output do Writeln('Очередь 1 начала выполняться');
  Sleep(500);
  lock output do Writeln('Очередь 1 закончила выполняться');
end);
var q2 := HPQ(()-&gt;
begin
  lock output do Writeln('Очередь 2 начала выполняться');
  Sleep(500);
  lock output do Writeln('Очередь 2 закончила выполняться');
end);

Writeln('Последовательное выполнение:');
Context.Default.SyncInvoke( q1 + q2 );

Writeln;
Writeln('Параллельное выполнение:');
Context.Default.SyncInvoke( q1 * q2 );
</code></pre>
<p>Операторы += и *= также применимы к очередям.<br />
И как и для чисел - <code>A += B</code> работает как <code>A := A+B</code> (и аналогично с *=).<br />
А значит, возвращаемые типы очередей A и B должны быть одинаковыми, чтобы к ним можно было применить +=/*=.</p>
<hr />
<p>Если надо сложить/умножить много очередей - лучше применять <code>CombineSyncQueue</code>/<code>CombineAsyncQueue</code> соответственно.<br />
Точнее <code>A+B+C</code> это то же самое что <code>CombineSyncQueue(CombineSyncQueue(A, B), C)</code>.<br />
Поэтому <code>CombineSyncQueue(A, B, C)</code> создаст очередь немного быстрее чем <code>A+B+C</code>.<br />
Но скорость выполнения очереди будет абсолютно одинаковой в этих двух случаях.</p>
<p>Чтобы складывать/умножать много <code>CommandQueueBase</code> и <code>CommandQueueNil</code> очередей
используются <code>Combine*QueueBase</code> и <code>Combine*QueueNil</code> соответственно.</p>
<hr />
<p>Используя <code>CombineConv*Queue</code> можно указать функцию преобразования, принимающую результаты всех скомбинированных очередей:</p>
<pre><code>## uses OpenCLABC;

var q1 := HFQ( ()-&gt;1 );
var q2 := HFQ( ()-&gt;2 );

// Выводит 2, то есть только результат последней очереди
// Так сделано из за вопросов производительности
Context.Default.SyncInvoke( q1+q2 ).Println;
// Но бывает так, что нужны результаты всех сложенных/умноженных очередей

// В таком случае надо использовать CombineConv*Queue
Context.Default.SyncInvoke(
  CombineConvSyncQueue(
    results-&gt;results.JoinToString, // Функция преобразования
    q1, q2
  )
).Println;
// Выводит строку &quot;1 2&quot;. Это то же самое, что вернёт &quot;Arr(1,2).JoinToString&quot;
</code></pre>
<p>И так же как с другими очередями с кодом на CPU - есть <code>Quick</code> версия:</p>
<pre><code>## uses OpenCLABC;

var q1 := HFQQ( ()-&gt;1 );
var q2 := HFQQ( ()-&gt;2 );

Context.Default.SyncInvoke( q1+q2 ).Println;

Context.Default.SyncInvoke(
  CombineQuickConvSyncQueue(
    results-&gt;results.JoinToString,
    q1, q2
  )
).Println;
</code></pre>
<hr />
<p>Если надо скомбинировать несколько очередей с разными типами результатов, но при этом использовать все результаты, можно использовать <code>CombineConv*QueueN*</code>:</p>
<pre><code>## uses OpenCLABC;

var q1 := HFQQ( ()-&gt;1 );
var q2 := HFQQ( ()-&gt;'abc' );

Context.Default.SyncInvoke( q1+q2 ).Println;

Context.Default.SyncInvoke(
  CombineConvSyncQueueN2(
    (r1,r2)-&gt;$'integer[{r1}]+string[{r2}]',
    q1, q2
  )
).Println;
</code></pre>
<p>У каждой из <code>CombineConv*QueueN*</code> так же есть <code>CombineQuickConv*QueueN*</code> версия.</p>
</div>
<script>on_page_added(document.getElementById("page-24"))</script>
<div id="page-25" page_name="Из повторения очереди" hidden=true>
<p><del>В данный момент всё ещё не работает... Но уже совсем скоро, правда-правда!</del></p>
</div>
<script>on_page_added(document.getElementById("page-25"))</script>
<div id="page-26" page_name="Множественное использование очереди" hidden=true>
<p>Одну и ту же очередь можно использовать несколько раз, в том числе одновременно:</p>
<pre><code>## uses OpenCLABC;
var Q := HPQQ(()-&gt;lock output do Writeln('Q выполнилась'));

var t1 := Context.Default.BeginInvoke(Q);
var t2 := Context.Default.BeginInvoke(Q*Q);

t1.Wait;
t2.Wait;
</code></pre>
<p>Но эта программа выведет &quot;Q выполнилась&quot; три раза, потому что при каждом упоминании - Q запускается ещё раз.</p>
<p>Это не всегда хорошо. К примеру, может быть так что <code>Q</code> содержит какой то затратный алгоритм. Или ввод значения с клавиатуры:</p>
<pre><code>## uses OpenCLABC;

var Q := HFQ(()-&gt;
begin
  lock input do
    Result := ReadInteger;
end);

Context.Default.SyncInvoke(CombineConvAsyncQueue(res-&gt;res,
  Q,
  Q.ThenQuickConvert(i-&gt;i*i),
  Q.ThenQuickConvert(i-&gt;i*i*i)
)).Println;
</code></pre>
<p>Эта программа запросит три разных значения, что не всегда то что надо.</p>
<hr />
<p>Чтобы использовать результат одной очереди несколько раз применяется <code>.Multiusable</code>:</p>
<pre><code>## uses OpenCLABC;

var Q := HFQ(()-&gt;
begin
  lock input do
    Result := ReadInteger;
end);
var Qs := Q.Multiusable;

// Теперь нет смысла в Async, потому что 
Context.Default.SyncInvoke(CombineConvAsyncQueue(res-&gt;res,
  Qs(),
  Qs().ThenQuickConvert(i-&gt;i*i),
  Qs().ThenQuickConvert(i-&gt;i*i*i)
)).Println;
</code></pre>
<p><code>.Multiusable</code> создаёт новую функцию, вызывая которую можно получить любое количество очередей,
у которых будет общий результат.</p>
<p>Каждый вызов <code>.Multiusable</code> создаёт именно новую функцию.<br />
Это значит, что если использовать результаты двух вызовов <code>.Multiusable</code> - исходная очередь выполнится два раза:</p>
<pre><code>## uses OpenCLABC;

var Q := HFQ(()-&gt;
begin
  lock input do
    Result := ReadInteger;
end);
var Q1s := Q.Multiusable;
var Q2s := Q.Multiusable;

Context.Default.SyncInvoke(CombineConvAsyncQueue(res-&gt;res,
  Q1s(),
  Q1s().ThenQuickConvert(i-&gt;i*i),
  Q2s().ThenQuickConvert(i-&gt;i*i*i)
)).Println;
</code></pre>
<hr />
<p>Очередь для которой вызвали <code>.Multiusable</code> начинает выполняться не ожидая другие очереди:</p>
<pre><code>## uses OpenCLABC;

var Q0 := HPQ(()-&gt;
begin
  Sleep(1000);
  Writeln('Q0');
end);

var Q1s := HPQ(()-&gt;
begin
  Writeln('Q1');
end).Multiusable;
var Q1 := Q1s();

// Q1 выполнит первым
Context.Default.SyncInvoke( Q0 + Q1 );
</code></pre>
<p>Чтобы <code>Q1</code> выполнялась после <code>Q0</code> - они должны быть сумированны под <code>.Multiusable</code>:</p>
<pre><code>## uses OpenCLABC;

var Q0 := HPQ(()-&gt;
begin
  Sleep(1000);
  Writeln('Q0');
end);

var Q1 := HPQ(()-&gt;
begin
  Writeln('Q1');
end);

var Qs := (Q0+Q1).Multiusable;
Context.Default.SyncInvoke( Qs() );
</code></pre>
<hr />
<p><code>.Multiusable</code> не работает между вызовами <code>Context.BeginInvoke</code>:</p>
<pre><code>## uses OpenCLABC;

var Q := HFQ(()-&gt;
begin
  lock input do
    Result := ReadInteger;
end);
var Qs := Q.Multiusable;

Context.Default.SyncInvoke( Qs()                          ).Println;
Context.Default.SyncInvoke( Qs().ThenQuickConvert(i-&gt;i*i) ).Println;
</code></pre>
<p>Эта программа запросит ввод два раза.</p>
<p>Если контекст у двух очередей общий - лучше объединить вызовы <code>Context.BeginInvoke</code>.
Так не только <code>.Multiusable</code> будет работать, но и выполнение будет в целом быстрее.</p>
<p>А если контекст разный - надо сохранять результат в переменную и использовать <code>Wait</code> очереди
(<a path="Из ожидания очередей">подробнее на странице ниже</a>).</p>
</div>
<script>on_page_added(document.getElementById("page-26"))</script>
<div id="page-27" page_name="Общие методы CCQ объектов" hidden=true>
<p>Между командами в <code>CCQ</code> очередях бывает надо вставить выполнение другой очереди или кода для CPU.</p>
<p>Это можно сделать, используя несколько <code>.NewQueue</code>:</p>
<pre><code>var s: MemorySegment;
var q0: CommandQueueBase;
...
var q :=
  s.NewQueue.ThenWriteValue(...) +
  
  q0 +
  HPQ(...) +
  HPQQ(...) +
  
  s.NewQueue.ThenWriteValue(...)
;
</code></pre>
<p>Однако можно сделать и красивее:</p>
<pre><code>var s: MemorySegment;
var q0: CommandQueueBase;
...
var q := s.NewQueue
  .ThenWriteValue(...)
  
  .ThenQueue(q0)
  .ThenProc(...)
  .ThenQuickProc(...)
  
  .ThenWriteValue(...)
;
</code></pre>
<p>(На самом деле процедура передаваемая в <code>.ThenProc</code>'а принимает исходную простую обёртку параметром, что делает его более похожим на <code>.ThenUse</code>, чем <code>HPQ</code>)</p>
<p>Эти методы не имеют незаменимых применений, но позволяют сделать код значительно читабельнее.</p>
</div>
<script>on_page_added(document.getElementById("page-27"))</script>
<div id="page-28" page_name="Из ожидания очередей" hidden=true>
<p>Большинство деревьев выполнения очередей можно реализовать используя только сложение и умножение очередей. Но есть несколько проблем:</p>
<ul>
<li><p>Большинство но не все. Пример дерева которое нельзя реализовать через сложение и умножение можно найти <a href="https://github.com/SunSerega/POCGL/blob/master/Samples/OpenCLABC/Wait%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8/1.pas">тут</a> или в файле:<br />
C:\PABCWork.NET\Samples\OpenGL и OpenCL\OpenCLABC\Wait очереди\1.pas</p>
</li>
<li><p>Даже если дерево реализуется сложением+умножением - такая реализация может выглядеть запутано и не похоже на график дерева выполнения, <a href="https://github.com/SunSerega/POCGL/blob/master/Samples/OpenCLABC/Wait%20%D0%BE%D1%87%D0%B5%D1%80%D0%B5%D0%B4%D0%B8/2.pas">к примеру</a>:<br />
C:\PABCWork.NET\Samples\OpenGL и OpenCL\OpenCLABC\Wait очереди\2.pas</p>
</li>
<li><p>Очереди, выполняемые на разных контекстах не могут эффективно взаимодействовать.</p>
</li>
</ul>
<p>Эти проблемы решают <code>Wait</code>-очереди:</p>
<pre><code>## uses OpenCLABC;

var M := WaitMarker.Create;

var t := Context.Default.BeginInvoke(
  HPQ(()-&gt;Writeln('Начало .BeginInvoke')) +
  WaitFor(M) +
  HPQ(()-&gt;Writeln('Конец .BeginInvoke'))
);

Sleep(2000);
Writeln('Посылаем сигнал маркера M');
M.SendSignal;

t.Wait;
</code></pre>
<p><code>WaitFor(M)</code> создаёт очень простую <code>Wait</code>-очередь.<br />
Внутри вызова <code>.BeginInvoke</code> (до того как он вернёт <code>CLTask</code>)
эта очередь входит в режим ожидания сигналов от маркера <code>M</code>.<br />
В этом режиме она <strong>не</strong> тратит время процессора, но готова возобновить выполнение в любой момент.</p>
<p>Далее, вызов <code>M.SendSignal</code> посылает по 1 сигналу всем <code>.BeginInvoke</code>, имеющим <code>Wait</code>-очереди в ожидании этого сигнала.</p>
<hr />
<p><code>WaitMarker</code> не является очередью, но может быть преобразован к типу <code>CommandQueueBase</code>, в своеобразную выполняемую форму.</p>
<p>Преобразование к выполняемой форме обычно происходит автоматически, если складывать/умножать его с другими очередями,
либо передавать в подпрограмму принимающую <code>CommandQueueBase</code>, как <code>.BeginInvoke</code>.<br />
Так же его можно вызвать явно, написав <code>CommandQueueBase(M)</code>, где <code>M</code> - маркер.</p>
<p>Выполняемая форма маркера вызывает метод <code>.SendSignal</code> своего макера.<br />
То есть <code>CommandQueueBase(M)</code> обычно равноценна, но немного эффективнее чем <code>HPQQ(M.SendSignal)</code>.<br />
Но не всегда - потому что <code>HPQ</code> возвращает <code>nil</code>, а выполняемая форма маркера может возвращать другие результаты (об этом ниже).</p>
<p>И стоит так же сказать, прямой вызов <code>M.SendSignal</code> всё равно всегда эффективнее чем <code>Context.Default.SyncInvoke(M)</code>.<br />
Используйте выполнение маркеров внутри <code>.BeginInvoke</code> только если вам надо активировать его сразу после других очередей:</p>
<pre><code>var M := WaitMarker.Create;

Context.Default.SyncInvoke(
  HFQ(()-&gt;5) + M
);
</code></pre>
<hr />
<p>Но в этом же коде видно ещё одну проблему - сложение очередей возвращает последний результат,
то есть результат маркера (который для простого макера из <code>WaitMarker.Create</code> - <code>nil</code>).</p>
<p>Если нужно иметь сразу и маркер и возвращаемое значение предыдущей очереди,
можно создать оторванный сигнал маркера методом <code>.ThenMarkerSignal</code>:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQ(()-&gt;5).ThenMarkerSignal;

var t := Context.Default.BeginInvoke(
  WaitFor(Q) + HPQ(()-&gt;Writeln('Получен сигнал от Q'))
);
var res := Context.Default.SyncInvoke( Q );

t.Wait;
res.Println;
</code></pre>
<p>Тут выполнение <code>Q</code> сначала выполнит <code>HFQ</code>, затем пошлёт сигнал в
<code>Wait</code>-очередь <code>WaitFor(Q)</code> и в конце вернёт то, что вернула <code>HFQ</code>.</p>
<p>В общем случае, оторванный сигнал макера является очередью, и имеет то же возвращаемое значение, что очередь из которой он был создан.<br />
Но в то же время его можно преобразовать типу <code>WaitMarker</code>, так же как <code>WaitMarker</code> преобразовывается в <code>CommandQueueBase</code>.<br />
Более того, выполняемая форма полученного маркера - это оторванный сигнал, из которого создали этот маркер.
Другими словами даже после преобразования в <code>WaitMarker</code> - оторванный сигнал можно выполнить и получить его результат:</p>
<pre><code>## uses OpenCLABC;
var Q := HFQ(()-&gt;5).ThenMarkerSignal;
var M: WaitMarker := Q;

Context.Default.SyncInvoke(
  CommandQueueBase(M)
  // CommandQueueBase надо всё равно как то преобразовать,
  // чтобы SyncInvoke вернуло результат Q
  .Cast&amp;&lt;integer&gt;
).Println;
</code></pre>
<hr />
<p>Того же эффекта можно добится используя <code>.Multiusable</code>:</p>
<pre><code>## uses OpenCLABC;

var Qs := HFQ(()-&gt;5).Multiusable;
var M := WaitMarker.Create;

var t := Context.Default.BeginInvoke(
  WaitFor(M) + HPQ(()-&gt;Writeln('Получен сигнал от M'))
);
var res := Context.Default.SyncInvoke( Qs()+M+Qs() );

t.Wait;
res.Println;
</code></pre>
<p>То есть <code>.ThenMarkerSignal</code> не имеет незаменимых применений, но делает код читабельнее.</p>
<hr />
<p>Есть всего три подпрограммы, создающие <code>Wait</code>-очереди:</p>
<ol>
<li><p>Глобальная, <code>WaitFor</code>:<br />
Ничего не делает сама, но блокирует выполнение, ожидая сигнала указанного маркера.</p>
</li>
<li><p>Ещё один общий метод CCQ объектов: <code>.ThenWait</code>:<br />
Как и <code>.ThenQueue</code> и <code>.ThenProc</code>, <code>.ThenWait(...)</code> это всего лишь аналог
<code>.ThenQueue(WaitFor(...))</code>, существующий только ради читабельности кода.</p>
</li>
<li><p>Метод очереди с результатом, <code>.ThenWaitFor</code>:<br />
Как и <code>.ThenMarkerSignal</code>, сначала выполняет исходную очередь, потом ожидает и в конце возвращает результат исходной очереди.</p>
</li>
</ol>
<hr />
<p>Маркеры можно комбинировать:</p>
<pre><code>## uses OpenCLABC;

var M1 := WaitMarker.Create;
var M2 := WaitMarker.Create;

var Q1 := WaitFor(M1 and M2); // Ожидание обоих маркеров
var Q2 := WaitFor(M1 or M2); // Ожидание любого из маркеров
</code></pre>
<p>При этом если надо применить <code>and</code> или <code>or</code> &gt;2 маркерам - лучше использовать <code>WaitAll</code>/<code>WaitAny</code> соответственно:</p>
<pre><code>## uses OpenCLABC;

var M1 := WaitMarker.Create;
var M2 := WaitMarker.Create;
var M3 := WaitMarker.Create;

var Q1 := WaitFor(WaitAll(M1,M2,M3)); // Ожидание всех маркеров
var Q2 := WaitFor(WaitAny(M1,M2,M3)); // Ожидание любого из маркеров
</code></pre>
<hr />
<p><code>Wait</code>-очереди работают даже между вызовами <code>Context.BeginInvoke</code>, в отличии от всего остального в <code>OpenCLABC</code>.</p>
<p>Это не всегда безопасно:</p>
<pre><code>Context.Default.BeginInvoke(M);
Context.Default.BeginInvoke(WaitFor(M) + Q);
</code></pre>
<p>Проблема этого кода в том, что <code>M</code> может послать сигнал ещё до того как <code>WaitFor(M)</code> начнёт ждать.</p>
<p>Чтобы такое не происходило - надо всегда запускать <code>Wait</code>-очередь раньше маркера:</p>
<pre><code>Context.Default.BeginInvoke(WaitFor(M) + Q);
Context.Default.BeginInvoke(M);
</code></pre>
<p>Но, как всегда, лучше объединять вызовы <code>Context.BeginInvoke</code>:</p>
<pre><code>Context.Default.BeginInvoke(
  ( M ) *
  ( WaitFor(M) + Q )
);
</code></pre>
<p>Все <code>Wait</code>-очереди начинают ждать в самом начале вызова <code>Context.BeginInvoke</code>, перед началом выполнения очереди.
Поэтому если <code>Wait</code>-очередь и вызов её маркера находятся в общем <code>Context.BeginInvoke</code> - использовать их можно в любом порядке.</p>
<hr />
<p>Все <code>Wait</code>-очереди в одном <code>Context.BeginInvoke</code>, ожидающие один и тот же маркер, образуют общую <code>Wait</code>-группу.<br />
Когда ожидаемый этой группой маркер активируется - он удаляет из <code>Wait</code>-группы одну из <code>Wait</code>-очередей, посылая ей сигнал.</p>
<pre><code>## uses OpenCLABC;

var Q1 := HPQ(()-&gt;
begin
  Sleep(10);
  lock output do Writeln('Выполнилась Q1');
end).ThenMarkerSignal;

var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));

var t1 := Context.Default.BeginInvoke(
  (WaitFor(Q1)+Q2) *
  (WaitFor(Q1)+Q3)
);
Context.Default.SyncInvoke(Q1+Q1);

// Все оставшиеся CLTask лучше ожидать, чтобы
// вывести ошибки, если возникнут при выполнении очереди
t1.Wait;
</code></pre>
<p>Тут <code>Q1</code> посылает 2 сигнала, сначала в первый <code>WaitFor(Q1)</code>, затем во второй.<br />
В данный момент не рекомендуется расчитывать на порядок <code>Wait</code>-очередей в <code>Wait</code>-группе.</p>
<p>Ну и, конечно, лучше совместить вызовы <code>Context.BeginInvoke</code>, раз контекст общий:</p>
<pre><code>## uses OpenCLABC;

var Q1 := HPQ(()-&gt;
begin
  Sleep(10);
  lock output do Writeln('Выполнилась Q1');
end).ThenMarkerSignal;

var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));

Context.Default.SyncInvoke(
  (Q1+Q1) *
  (WaitFor(Q1)+Q2) *
  (WaitFor(Q1)+Q3)
);
</code></pre>
<p>Будьте осторожны, лишняя <code>Wait</code>-очередь вызовет зависание:</p>
<pre><code>## uses OpenCLABC;

var Q1 := HFQ(()-&gt;0).ThenMarkerSignal;

var t1 := Context.Default.BeginInvoke(
  WaitFor(Q1) +
  // Второй запуск Q1 никогда не произойдёт,
  // поэтому эта Wait очередь зависнет
  WaitFor(Q1)
);
Context.Default.SyncInvoke(Q1);

t1.Wait;
</code></pre>
<hr />
<p><code>Wait</code>-очереди ожидающие один и тот же маркер в разных <code>Context.BeginInvoke</code> - образуют отдельные <code>Wait</code>-группы.<br />
И при активации маркера - он посылает по 1 сигналу <strong>каждой</strong> (ожидающей его) <code>Wait</code>-группе:</p>
<pre><code>## uses OpenCLABC;

var Q1 := HPQ(()-&gt;
begin
  Sleep(1000);
  lock output do Writeln('Выполнилась Q1');
end).ThenMarkerSignal;

var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));

var Q4 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q4'));
var Q5 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q5'));

var t1 := Context.Default.BeginInvoke(
  ( WaitFor(Q1)+Q2 ) *
  ( WaitFor(Q1)+Q3 )
);
var t2 := Context.Default.BeginInvoke(
  ( WaitFor(Q1)+Q4 ) *
  ( WaitFor(Q1)+Q5 )
);
// Каждый вызов Q1 тут - активирует по одному WaitFor(Q1) в каждом .BeginInvoke
Context.Default.SyncInvoke(Q1+Q1);

t1.Wait;
t2.Wait;
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-28"))</script>
<div id="page-29" page_name="Обработка исключений" hidden=true>
<p>Для начала немного о распространении ошибок в очередях:</p>
<p>Исключения в очереди Q отменяет выполнение всех очередей, ожидающих Q,
но никак не влияет на параллельные к Q очереди:</p>
<pre><code>## uses OpenCLABC;

function make_q(i: integer) :=
HPQ(()-&gt;lock output do Writeln(i));

try
  Context.Default.SyncInvoke(
    make_q(1) +
    (make_q(2) * HPQ(()-&gt;raise new Exception('3')))
    + make_q(4)
  );
except
  on e: System.AggregateException do
    Writeln(e.InnerExceptions.Single);
end;
</code></pre>
<p>Очередь 1 выполняется в любом случае, потому что очередь с ошибкой (3) находится позже;<br />
Очередь 2 выполняется, потому что она стоит параллельно к очереди 3;<br />
Очередь 3 выполняется и в процессе кидает исключение;<br />
Очередь 4 пропускается, потому что ожидает успешного выполнения очередей 2 и 3.</p>
<p>Может звучать тривиально - но это базовое правило, распространяющееся на более сложные случаи. К примеру:</p>
<pre><code>## uses OpenCLABC;

var M := WaitMarker.Create;

try
  Context.Default.SyncInvoke(
    (HPQ(()-&gt;raise new Exception('Special error')) + WaitFor(M))
  *
    (HPQ(()-&gt;Sleep(100)) + WaitFor(M))
  *
    M
  );
except
  on e: System.AggregateException do
    Writeln(e.InnerExceptions.Single);
end;
</code></pre>
<p>Из за исключения первое <code>WaitFor(M)</code> пропускается. Оно начинает ожидать
ещё до выхода из <code>.BeginInvoke</code>, но затем отменяется, а значит не имеет
шанса поглотить сигнал от <code>M</code>, оставляя его для второго <code>WaitFor(M)</code>.</p>
<hr />
<p>Так же надо сказать что <code>.Multiusable</code> даёт множественный доступ
к любому результату выполнения очереди, будь то возвращаемое значение или ошибка:</p>
<pre><code>## uses OpenCLABC;

var Qs := HPQ(()-&gt;raise new Exception('Special error')).Multiusable;

try
  Context.Default.SyncInvoke( Qs()*Qs() );
except
  on e: System.AggregateException do
    e.InnerExceptions.PrintLines;
end;
</code></pre>
<p>Этот код выводит ошибку дважды, потому что она пришла из 2 параллельных веток, которые обе взяли её из общего источника.</p>
<hr />
<p>Самый простой обработчик ошибок это <code>.HandleWithoutRes</code>:<br />
Он применим ко всем очередям, в том числе <code>CommandQueueBase</code>, потому что игнорирует любое предыдущее
возвращаемое значение и создаёт <code>CommandQueueNil</code>.</p>
<p>Есть два варианта вызова <code>.HandleWithoutRes</code>:</p>
<ol>
<li>Ловля всех исключений - аналог <code>on e: Exception do</code>:
<pre><code>## uses OpenCLABC;

var Q1 := HPQ(()-&gt;raise new Exception('Expected error'));
var Q2 := HPQ(()-&gt;raise new Exception('Unexpected error'));

try
  Context.Default.SyncInvoke( (Q1*Q2)
    // Ловим все исключения с указанным сообщением
    // Для реального случая это плохо, но для демонстрации нормально
    .HandleWithoutRes(e-&gt;(e.Message.Print='Expected error').Println)
  );
except
  on e: System.AggregateException do
    e.InnerExceptions.PrintLines;
end;
</code></pre>
</li>
<li>Ловля исключений конкретного типа - аналог <code>on e: ... do</code>:
<pre><code>## uses OpenCLABC;

var Q1 := HPQ(()-&gt;raise new System.InvalidOperationException);
var Q2 := HPQ(()-&gt;raise new System.InvalidTimeZoneException);

try
  Context.Default.SyncInvoke( (Q1*Q2)
    // Ловим все исключения типа InvalidOperationException
    // Опять же, вообще плохо съедать исключения без полной диагностики
    .HandleWithoutRes&amp;&lt;System.InvalidOperationException&gt;(e-&gt;true)
  );
except
  on e: System.AggregateException do
    e.InnerExceptions.PrintLines;
end;
</code></pre>
</li>
</ol>
<hr />
<p>Далее, обработчик <code>.HandleDefaultRes</code>:</p>
<p>Если в очереди было кинуто исключение - возвращаемое значение не существует (не <code>nil</code>, а именно отсутствует).<br />
Поэтому чтобы вернуть очередь с определённым типом возвращаемого значения,
необходимо заменить результат после успешной обработки исключения:</p>
<pre><code>## uses OpenCLABC;

// Исключение, кидаемое при не_первых выполнениях этой очереди
var need_error := false;
var QErr := HPQ(()-&gt;
begin
  if need_error then
    raise new Exception else
    need_error := true;
end);

loop 3 do Context.Default.SyncInvoke( (QErr+HFQ(()-&gt;5))
  // Ловим все исключения типа Exception
  // И заменяем результат на 0, если он отсутствует
  .HandleDefaultRes&amp;&lt;Exception&gt;(e-&gt;true, 0)
).Println;
</code></pre>
<hr />
<p>И последний - обработчик <code>.HandleReplaceRes</code>:</p>
<p>Не всегда значение, которым надо заменять возвращаемое значение очереди, можно посчитать до выполнения этой очереди.<br />
В более сложном случае это значение может вычисляться во время выполнения, скорее всего используя пойманные исключения.</p>
<p>В таком случае нет смысла обрабатывать исключения по одному, потому что так
можно получить несколько конфликтующих замен для возвращаемого значения:</p>
<pre><code>## uses OpenCLABC;
var QErr := HPQ(()-&gt;raise new Exception);

Context.Default.SyncInvoke(
  (QErr*QErr + HFQ(()-&gt;-1))
  .HandleReplaceRes(lst-&gt;
  begin
    Result := lst.Count; // Возвращаемое значение
    lst.Clear; // Считаем что обработали всё
  end)
).Println;
</code></pre>
<p><code>.HandleReplaceRes</code> нельзя вызывать на <code>Base</code> и <code>Nil</code> очередях, потому что непонятно что возвращать если ошибка <strong>не</strong> возникла.
Но всегда можно прибавить константную очередь к обрабатываемой, таким образом указав результат по-умолчанию, на случай без ошибок:</p>
<pre><code>## uses OpenCLABC;

var Q1 := HPQQ(()-&gt;begin end);
var Q2 := HPQQ(()-&gt;raise new Exception);

Context.Default.SyncInvoke((Q1*Q2 + CQ(0)).HandleReplaceRes(err_lst-&gt;
begin
  err_lst.PrintLines;
  err_lst.Clear;
  Result := -1;
end)).Println;
</code></pre>
<p>Результат очереди из этого примера можно передавать напрямую в <code>Halt</code>, если вы, к примеру, делаете инструмент для коммандной строки.</p>
<hr />
<p>Обратите внимание, обработчики исключений всегда действую по принципу <code>Quick</code> очередей.<br />
То есть код выполняемый на CPU, который обрабатывает исключения, не должен содержать блокирующих вызовов модуля <code>OpenCL</code>.</p>
<p>Но использовать затратные алгоритмы, или блокирующие вызовы как ввод с клавиатуры (<code>'Ошибка, всё равно продолжить? [Y/n]'</code>) вполне нормально:<br />
Обработчики вызываются только если было кинуто исключение. А исключения должны кидаться только в исключительных ситуациях, когда что-то пошло не так.</p>
<hr />
<p>В паскале есть два типа блоков для обработки исключений, <code>try-except</code> - собственно обработчик,
а так же <code>try-finally</code> - позволяющий выполнять код не зависимо от того, было ли кинуто исключение.</p>
<p>В очередях <code>OpenCLABC</code> аналог последнего выглядит так:</p>
<pre><code>## uses OpenCLABC;
function QErr(i: integer) := HPQ(()-&gt;raise new Exception(i.ToString));

Context.Default.SyncInvoke(
  ( QErr(1)+QErr(2) &gt;= QErr(3)+QErr(4) &gt;= QErr(5)+QErr(6) )
  .HandleWithoutRes(e-&gt;e.Message.Println&lt;&gt;nil)
);
</code></pre>
<p>В этом коде:</p>
<ul>
<li>Выполняется очередь 1, кидая исключение.</li>
<li>Очередь 2 пропускается, из за ошибки в очереди 1.</li>
<li>Затем стоит <code>&gt;=</code>, работающий как <code>+</code>, но игнорирующий предыдущие исключения, поэтому очередь 3 выполняется.</li>
<li>Очередь 4 пропускается, из за ошибки в очереди 3.</li>
<li>Затем стоит ещё один <code>&gt;=</code>, игнорирующий исключения всех предыдущих очередей, то есть 1 и 3, а значит очередь 5 тоже выполняется.</li>
<li>Очередь 6 пропускается, из за ошибки в очереди 5.</li>
</ul>
<p>Затем стоит обработчик, который съедает все исключения, но перед этим выводит их текст, показывая что выполнились очереди 1, 3 и 5.</p>
<hr />
<p>Обычные <code>.ThenMarkerSignal</code> и <code>.ThenWaitFor</code> посылают/съедают сигнал маркера только если перед ними небыло ошибок.<br />
Это не всегда хорошо, потому что в случае ошибок будет сложно судить о количестве сигналов.</p>
<p>Как общее правило - <code>Wait</code>-очереди и маркеры лучше ставить после <code>&gt;=</code>.
Но <code>HFQ(()-&gt;5) &gt;= WaitFor(M)</code> вернёт результат последней очереди, то есть <code>nil</code>.</p>
<p>Поэтому существуют методы <code>.ThenFinallyMarkerSignal</code> и <code>.ThenFinallyWaitFor</code>.<br />
Эти методы так же как их варианты без <code>Finally</code> - возвращают то что вернула исходная очередь.<br />
Но в отличии от них - посылают/поглащают сигнал не зависимо от ошибок в очереди, из которой их создали.</p>
</div>
<script>on_page_added(document.getElementById("page-29"))</script>
<div id="page-30" page_name="Не создавая явно" hidden=true>
<p>Передавать команды по одной, когда их несколько - ужасно не эффективно!<br />
Но нередко бывает так, что команда всего одна. Или для отладки надо одноразово выполнить несколько команд.</p>
<p>Для таких случаев можно создавать очередь неявно:<br />
У каждого метода для создания комманды для GPU есть дублирующий метод в простой обёртке. Этот метод сам создаёт
новую очередь, добавляет в неё одну соответствующую команду и выполняет полученную очередь в <code>Context.Default.SyncInvoke(...)</code>.</p>
<p>Обычный код с очередями:</p>
<pre><code>## uses OpenCLABC;

var a := new CLArray&lt;integer&gt;(3);

Context.Default.SyncInvoke(a.NewQueue
  .ThenWriteValue(1, 0)
  .ThenWriteArray(|5,7|, 1, 2,0)
  .ThenGetArray
).Println;
</code></pre>
<p>Он же, но с неявными очередями:</p>
<pre><code>## uses OpenCLABC;

var a := new CLArray&lt;integer&gt;(3);

a.WriteValue(1, 0);
a.WriteArray(|5,7|, 1, 2,0);
a.GetArray.Println;
</code></pre>
<p>И в случае <code>CLArray</code> можно ещё красивее:</p>
<pre><code>## uses OpenCLABC;

var a := new CLArray&lt;integer&gt;(3);

a[0] := 1;
a.Section[1..2] := |5,7|;
a.Section[0..2].Println;
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-30"))</script>
<script>on_end_folder()</script>
<div id="page-31" page_name="" hidden=true>
<p>Все методы создающие одну команду (<code>*CCQ.Add*</code> методы и все методы неявных очередей)
могут принимать очередь вместо значения в качестве практически любого параметра. Но в таком случае
возвращаемый тип очереди должен совпадать с типом параметра. К примеру:</p>
<pre><code>## uses OpenCLABC;

var a := new CLArray&lt;integer&gt;(10);
// Очищаем весь буфер нулями, чтобы не было мусора
a.FillValue(0);

// Второй параметр AddWriteItem - индекс в массиве
// Он имеет тип integer, а значит можно передать и CommandQueue&lt;integer&gt;
// Таким образом, в параметр сохраняется алгоритм, а не готовое значение
// Поэтому 3 вызова ниже могут получится с 3 разными индексами
var q := a.NewQueue
  .ThenWriteValue(5, HFQQ(()-&gt; Random(a.Length) ))
;

Context.Default.SyncInvoke(q);
Context.Default.SyncInvoke(q);
Context.Default.SyncInvoke(q);

a.GetArray.Println;
</code></pre>
<p>Все вложенные очереди начинают выполняться сразу при вызове метода <code>Context.BeginInvoke</code>, не ожидая других очередей.</p>
<p>Обычно вложенные очереди особо полезны когда надо записать что-то в память GPU прямо перед вызовом kernel'а:</p>
<pre><code>k.Exec1(N, a.NewQueue.ThenFillValue(1))
</code></pre>
<p>Вместо:</p>
<pre><code>a.NewQueue.ThenFillValue(1) +
k.Exec1(N, a)
</code></pre>
</div>
<script>on_start_folder("Вложенные очереди", document.getElementById("page-31"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
<div id="page-32" page_name="" hidden=true>
<p>В данной справке в нескольких местах можно встретить утверждения вроде</p>
<blockquote>
<p>Вызовы <code>Context.BeginInvoke</code> стоит, по возможности, объединять.</p>
</blockquote>
<p>Данный раздел подпробнее объясняет устройство модуля, что делает подобные утверждения более понятными.</p>
<p>Читать его не необходимо для написания работающего кода, но желательно для написания качественного кода.</p>
<p>Но стоит сказать заранее - это не полное объяснение внутренностей модуля. Объясняется только то, что скорее всего окажется нужным.
Если хотите ещё более полное понимание - используйте Ctrl+клик в IDE по именам, чтобы смотреть исходный код.</p>
<hr />
<h1>Страницы:</h1>
<ul>
<li><a path="Внутренности BeginInvoke">	Что делает вызов <code>BeginInvoke</code>	</a></li>
</ul>
</div>
<script>on_start_folder("Оптимизация", document.getElementById("page-32"))</script>
<div id="page-33" page_name="Внутренности BeginInvoke" hidden=true>
<p>Кроме самого выполнения очередей - им так же необходима инициализация и финализация.</p>
<hr />
<h3>Инициализация</h3>
<p>Для начала, перед тем как любая из под-очередей в <code>BeginInvoke</code> начнёт выполняться - необходимо инициалировать <code>Wait</code> очереди.
Иначе у ожидаемого маркера всегда будет шанс выполнится до того как ожидающая его очередь начнёт ожидать.</p>
<p>Инициализация <code>Wait</code> очередей заключается в обходе всего дерева под-очередей.
Для каждой <code>Wait</code>-группы (очередей, ожидающих общий маркер в общем <code>.BeginInvoke</code>),
создаётся счётчик выполнений ожидаемого маркера.</p>
<hr />
<h3>Запуск очередей</h3>
<p>Тоже заключается в обходе дерева под-очередей.</p>
<p>Но в этот раз очереди по которым прошлись - уже начали выполнятся, не ожидая окончания обхода всего дерева.</p>
<p>Как только этот обход закончен - метод <code>BeginInvoke</code> возвращает свой <code>CLTask</code> коду, вызвавшему его.</p>
<hr />
<h3>Финализация</h3>
<p>Заключается в чистке после выполнения - к примеру удалении всех созданных <code>cl_command_queue</code>.<br />
Так же тут собирается информация о всех исключениях, пойманных во время выполнения.</p>
<hr />
<p>Основное преимущество объединения вызовов <code>BeginInvoke</code> состоит в различии следующих 2 случаев:</p>
<pre><code>Context.Default.SyncInvoke(A+B);
</code></pre>
<pre><code>Context.Default.SyncInvoke(A);
Context.Default.SyncInvoke(B);
</code></pre>
<p>В первом случае пока выполнится <code>A</code> - <code>B</code> уже, скорее всего, окажется полностью запущено.
А значит как только <code>A</code> закончит выполнятся - ход выполнения перейдёт на <code>B</code>.</p>
<p>А во втором случае - между окончанием выполнения <code>A</code> и запуском <code>B</code> - будет произведено множество проверок,
а так же выходов/входов в объёмные (что значит JIT их не инлайнит) подпрограммы, как конструктор <code>CLTask</code>.</p>
<p>Так же, в первом случае многие ресурсы, как объекты <code>OpenCL.cl_command_queue</code>,
выделенные при выполнении <code>A</code>, будут ещё раз использованы для выполнения <code>B</code>.</p>
<p>Да, всё это мелочи. Но нулевая задержка всегда лучше ненулевой.</p>
<hr />
<p>Ну а когда всё же приходится вызывать 2 отдельных <code>BeginInvoke</code>, к примеру на 2 разных
контекстах - можно использовать <code>Wait</code> очереди, чтобы добится того же эффекта:</p>
<pre><code>c2.BeginInvoke(WaitFor(A)+B);
c1.BeginInvoke(A);
</code></pre>
<p>Внутренние оптимизации <code>OpenCLABC</code> делают этот код практически не отличимым по скорости, от <code>BeginInvoke(A+B)</code>.</p>
<p>Единственное различие - время инициализации. Потому что <code>A</code> не запустится, пока не закончится вызов <code>c2.BeginInvoke</code>.</p>
</div>
<script>on_page_added(document.getElementById("page-33"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
</body>
</html>
